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
18 * along with this program. If not, see <https://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
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
409STRING_TUPLE_LIST::const_iterator
410PLUGIN_CONTENT_MANAGER::findRepository( const wxString& aRepositoryId ) const
411{
412 return std::find_if( m_repository_list.begin(), m_repository_list.end(),
413 [&aRepositoryId]( const std::tuple<wxString, wxString, wxString>& aRepo )
414 {
415 return std::get<0>( aRepo ) == aRepositoryId;
416 } );
417}
418
419
420const PCM_REPOSITORY&
421PLUGIN_CONTENT_MANAGER::getCachedRepository( const wxString& aRepositoryId ) const
422{
423 wxASSERT_MSG( m_repository_cache.find( aRepositoryId ) != m_repository_cache.end(),
424 wxT( "Repository is not cached." ) );
425
426 return m_repository_cache.at( aRepositoryId );
427}
428
429
430bool PLUGIN_CONTENT_MANAGER::CacheRepository( const wxString& aRepositoryId )
431{
432 if( m_repository_cache.find( aRepositoryId ) != m_repository_cache.end() )
433 return true;
434
435 const auto repository_tuple = findRepository( aRepositoryId );
436
437 if( repository_tuple == m_repository_list.end() )
438 return false;
439
440 wxString url = std::get<2>( *repository_tuple );
441
442 nlohmann::json js;
443 PCM_REPOSITORY current_repo;
444 PCM_REPOSITORY& current_repo_ref = current_repo;
445
446 std::shared_ptr<PROGRESS_REPORTER> reporter;
447
448 if( m_dialog )
449 reporter = std::make_shared<WX_PROGRESS_REPORTER>( m_dialog, wxT( "" ), 1, PR_CAN_ABORT );
450 else
451 reporter = m_updateBackgroundJob->m_reporter;
452
453 if( !FetchRepository( url, current_repo, reporter.get() ) )
454 return false;
455
456 bool packages_cache_exists = false;
457
458 // First load repository data from local filesystem if available.
459 wxFileName repo_cache = wxFileName( PATHS::GetUserCachePath(), wxT( "repository.json" ) );
460 repo_cache.AppendDir( wxT( "pcm" ) );
461 repo_cache.AppendDir( aRepositoryId );
462 wxFileName packages_cache( repo_cache.GetPath(), wxT( "packages.json" ) );
463
464 if( repo_cache.FileExists() && packages_cache.FileExists() )
465 {
466 std::ifstream repo_stream( repo_cache.GetFullPath().fn_str() );
467 PCM_REPOSITORY saved_repo;
468 try
469 {
470 repo_stream >> js;
471 saved_repo = js.get<PCM_REPOSITORY>();
472 }
473 catch( ... )
474 {
475 if( m_dialog )
476 wxLogError( _( "Failed to parse locally stored repository.json." ) );
477 }
478
479 if( saved_repo.packages.update_timestamp == current_repo.packages.update_timestamp )
480 {
481 // Cached repo is up to date, use data on disk
482 js.clear();
483 std::ifstream packages_cache_stream( packages_cache.GetFullPath().fn_str() );
484
485 try
486 {
487 packages_cache_stream >> js;
488 saved_repo.package_list = js["packages"].get<std::vector<PCM_PACKAGE>>();
489
490 for( size_t i = 0; i < saved_repo.package_list.size(); i++ )
491 {
492 PreparePackage( saved_repo.package_list[i] );
493 saved_repo.package_map[saved_repo.package_list[i].identifier] = i;
494 }
495
496 m_repository_cache[aRepositoryId] = std::move( saved_repo );
497
498 packages_cache_exists = true;
499 }
500 catch( ... )
501 {
502 if( m_dialog )
503 {
504 wxLogError( _( "Packages cache for current repository is corrupted, it will "
505 "be redownloaded." ) );
506 }
507 }
508 }
509 }
510
511 if( !packages_cache_exists )
512 {
513 // Cache doesn't exist or is out of date
514 if( !fetchPackages( current_repo.packages.url, current_repo.packages.sha256,
515 current_repo.package_list, reporter.get(),
516 current_repo.schema_version ) )
517 {
518 return false;
519 }
520
521 for( size_t i = 0; i < current_repo.package_list.size(); i++ )
522 {
523 PreparePackage( current_repo.package_list[i] );
524 current_repo.package_map[current_repo.package_list[i].identifier] = i;
525 }
526
527 repo_cache.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
528
529 std::ofstream repo_cache_stream( repo_cache.GetFullPath().fn_str() );
530 repo_cache_stream << std::setw( 4 ) << nlohmann::json( current_repo ) << std::endl;
531
532 std::ofstream packages_cache_stream( packages_cache.GetFullPath().fn_str() );
533 js.clear();
534 js["packages"] = nlohmann::json( current_repo.package_list );
535 packages_cache_stream << std::setw( 4 ) << js << std::endl;
536
537 m_repository_cache[aRepositoryId] = std::move( current_repo );
538 current_repo_ref = m_repository_cache[aRepositoryId];
539 }
540
541 if( current_repo_ref.resources )
542 {
543 // Check resources file date, redownload if needed
544 PCM_RESOURCE_REFERENCE& resources = *current_repo_ref.resources;
545
546 wxFileName resource_file( repo_cache.GetPath(), wxT( "resources.zip" ) );
547
548 time_t mtime = 0;
549
550 if( resource_file.FileExists() )
551 mtime = wxFileModificationTime( resource_file.GetFullPath() );
552
553 if( mtime + 600 < getCurrentTimestamp() && mtime < (time_t) resources.update_timestamp )
554 {
555 std::ofstream resources_stream( resource_file.GetFullPath().fn_str(),
556 std::ios_base::binary );
557
558 reporter->SetTitle( _( "Downloading resources" ) );
559
560 // 100 Mb resource file limit
561 bool success = DownloadToStream( resources.url, &resources_stream, reporter.get(),
562 100 * 1024 * 1024 );
563
564 resources_stream.close();
565
566 if( success )
567 {
568 std::ifstream read_stream( resource_file.GetFullPath().fn_str(),
569 std::ios_base::binary );
570
571
572 if( resources.sha256 && !VerifyHash( read_stream, *resources.sha256 ) )
573 {
574 read_stream.close();
575
576 if( m_dialog )
577 {
578 wxLogError( _( "Resources file hash doesn't match and will not be used. "
579 "Repository may be corrupted." ) );
580 }
581
582 wxRemoveFile( resource_file.GetFullPath() );
583 }
584 }
585 else
586 {
587 // Not critical, just clean up the file
588 wxRemoveFile( resource_file.GetFullPath() );
589 }
590 }
591 }
592
593 updateInstalledPackagesMetadata( aRepositoryId );
594
595 return true;
596}
597
598
600{
601 const PCM_REPOSITORY* repository;
602
603 try
604 {
605 repository = &getCachedRepository( aRepositoryId );
606 }
607 catch( ... )
608 {
609 wxLogTrace( tracePcm, wxS( "Invalid/Missing repository " ) + aRepositoryId );
610 return;
611 }
612
613 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
614 {
615 PCM_INSTALLATION_ENTRY& entry = pair.second;
616
617 // If current package is not from this repository, skip it
618 if( entry.repository_id != aRepositoryId )
619 continue;
620
621 // If current package is no longer in this repository, keep it as is
622 if( repository->package_map.count( entry.package.identifier ) == 0 )
623 continue;
624
625 std::optional<PACKAGE_VERSION> current_version;
626
627 auto current_version_it =
628 std::find_if( entry.package.versions.begin(), entry.package.versions.end(),
629 [&]( const PACKAGE_VERSION& version )
630 {
631 return version.version == entry.current_version;
632 } );
633
634 if( current_version_it != entry.package.versions.end() )
635 current_version = *current_version_it; // copy
636
637 // Copy repository metadata into installation entry
638 entry.package = repository->package_list[repository->package_map.at( entry.package.identifier )];
639
640 // Insert current version if it's missing from repository metadata
641 current_version_it =
642 std::find_if( entry.package.versions.begin(), entry.package.versions.end(),
643 [&]( const PACKAGE_VERSION& version )
644 {
645 return version.version == entry.current_version;
646 } );
647
648 if( current_version_it == entry.package.versions.end() && current_version )
649 {
650 entry.package.versions.emplace_back( *current_version );
651
652 // Re-sort the versions by descending version
653 std::sort( entry.package.versions.begin(), entry.package.versions.end(),
654 []( const PACKAGE_VERSION& a, const PACKAGE_VERSION& b )
655 {
656 return a.parsed_version > b.parsed_version;
657 } );
658 }
659 }
660}
661
662
664{
665 // Parse package version strings
666 for( PACKAGE_VERSION& ver : aPackage.versions )
667 {
668 int epoch = 0, major = 0, minor = 0, patch = 0;
669
670 if( ver.version_epoch )
671 epoch = *ver.version_epoch;
672
673 wxStringTokenizer version_tokenizer( ver.version, "." );
674
675 major = wxAtoi( version_tokenizer.GetNextToken() );
676
677 if( version_tokenizer.HasMoreTokens() )
678 minor = wxAtoi( version_tokenizer.GetNextToken() );
679
680 if( version_tokenizer.HasMoreTokens() )
681 patch = wxAtoi( version_tokenizer.GetNextToken() );
682
683 ver.parsed_version = std::make_tuple( epoch, major, minor, patch );
684
685 // Determine compatibility
686 ver.compatible = true;
687
688 auto parse_version_tuple =
689 []( const wxString& version, int deflt )
690 {
691 int ver_major = deflt;
692 int ver_minor = deflt;
693 int ver_patch = deflt;
694
695 wxStringTokenizer tokenizer( version, "." );
696
697 ver_major = wxAtoi( tokenizer.GetNextToken() );
698
699 if( tokenizer.HasMoreTokens() )
700 ver_minor = wxAtoi( tokenizer.GetNextToken() );
701
702 if( tokenizer.HasMoreTokens() )
703 ver_patch = wxAtoi( tokenizer.GetNextToken() );
704
705 return std::tuple<int, int, int>( ver_major, ver_minor, ver_patch );
706 };
707
708 if( parse_version_tuple( ver.kicad_version, 0 ) > m_kicad_version )
709 ver.compatible = false;
710
711 if( ver.kicad_version_max
712 && parse_version_tuple( *ver.kicad_version_max, 999 ) < m_kicad_version )
713 ver.compatible = false;
714
715#if defined( _WIN32 )
716 wxString platform = wxT( "windows" );
717#elif defined( __APPLE__ )
718 wxString platform = wxT( "macos" );
719#else
720 wxString platform = wxT( "linux" );
721#endif
722
723 if( ver.platforms.size() > 0
724 && std::find( ver.platforms.begin(), ver.platforms.end(), platform )
725 == ver.platforms.end() )
726 {
727 ver.compatible = false;
728 }
729 else if( UsesSWIGRuntime( aPackage, ver.version ) )
730 {
731 ver.compatible = false;
732 }
733 }
734
735 // Sort by descending version
736 std::sort( aPackage.versions.begin(), aPackage.versions.end(),
737 []( const PACKAGE_VERSION& a, const PACKAGE_VERSION& b )
738 {
739 return a.parsed_version > b.parsed_version;
740 } );
741}
742
743
744bool PLUGIN_CONTENT_MANAGER::UsesSWIGRuntime( const PCM_PACKAGE& aPackage, const wxString& aVersion )
745{
746 if( !( aPackage.type == PT_PLUGIN || aPackage.type == PT_FAB ) )
747 return false;
748
749 auto ver_it = std::find_if( aPackage.versions.begin(), aPackage.versions.end(),
750 [&]( const PACKAGE_VERSION& ver )
751 {
752 return ver.version == aVersion;
753 } );
754
755 if( ver_it == aPackage.versions.end() )
756 return false;
757
758 return ver_it->runtime.value_or( PCM_PACKAGE_RUNTIME::PPR_SWIG ) == PCM_PACKAGE_RUNTIME::PPR_SWIG;
759}
760
761
762const std::vector<PCM_PACKAGE>&
763PLUGIN_CONTENT_MANAGER::GetRepositoryPackages( const wxString& aRepositoryId ) const
764{
765 static std::vector<PCM_PACKAGE> empty{};
766
767 try
768 {
769 return getCachedRepository( aRepositoryId ).package_list;
770 }
771 catch( ... )
772 {
773 return empty;
774 }
775}
776
777
779{
780 // Clean up cache folder if repository is not in new list
781 for( const std::tuple<wxString, wxString, wxString>& entry : m_repository_list )
782 {
783 auto it = std::find_if( aRepositories.begin(), aRepositories.end(),
784 [&]( const auto& new_entry )
785 {
786 return new_entry.first == std::get<1>( entry );
787 } );
788
789 if( it == aRepositories.end() )
790 {
791 DiscardRepositoryCache( std::get<0>( entry ) );
792 }
793 }
794
795 m_repository_list.clear();
796 m_repository_cache.clear();
797
798 for( const std::pair<wxString, wxString>& repo : aRepositories )
799 {
800 std::string url_sha = picosha2::hash256_hex_string( repo.second );
801 m_repository_list.push_back( std::make_tuple( url_sha.substr( 0, 16 ), repo.first,
802 repo.second ) );
803 }
804}
805
806
807void PLUGIN_CONTENT_MANAGER::DiscardRepositoryCache( const wxString& aRepositoryId )
808{
809 if( m_repository_cache.count( aRepositoryId ) > 0 )
810 m_repository_cache.erase( aRepositoryId );
811
812 wxFileName repo_cache = wxFileName( PATHS::GetUserCachePath(), "" );
813 repo_cache.AppendDir( wxT( "pcm" ) );
814 repo_cache.AppendDir( aRepositoryId );
815
816 if( repo_cache.DirExists() )
817 {
818 if( !repo_cache.Rmdir( wxPATH_RMDIR_RECURSIVE ) )
819 {
820 // best efforts
821 }
822 }
823}
824
825
827{
828 for( const auto& [repositoryId, cache] : m_repository_cache )
829 {
830 wxFileName repo_cache = wxFileName( PATHS::GetUserCachePath(), "" );
831 repo_cache.AppendDir( wxT( "pcm" ) );
832 repo_cache.AppendDir( repositoryId );
833
834 if( repo_cache.DirExists() )
835 {
836 if( !repo_cache.Rmdir( wxPATH_RMDIR_RECURSIVE ) )
837 {
838 // best efforts
839 }
840 }
841 }
842
843 m_repository_cache.clear();
844
845}
846
847
848void PLUGIN_CONTENT_MANAGER::MarkInstalled( const PCM_PACKAGE& aPackage, const wxString& aVersion,
849 const wxString& aRepositoryId )
850{
851 // In case of package update remove old data but keep pinned state
852 bool pinned = false;
853
854 if( m_installed.count( aPackage.identifier ) )
855 {
856 pinned = m_installed.at( aPackage.identifier ).pinned;
857 MarkUninstalled( aPackage );
858 }
859
861 entry.package = aPackage;
862 entry.current_version = aVersion;
863 entry.repository_id = aRepositoryId;
864
865 try
866 {
867 if( !aRepositoryId.IsEmpty() )
868 entry.repository_name = getCachedRepository( aRepositoryId ).name;
869 else
870 entry.repository_name = _( "Local file" );
871 }
872 catch( ... )
873 {
874 entry.repository_name = _( "Unknown" );
875 }
876
878 entry.pinned = pinned;
879
880 m_installed.emplace( aPackage.identifier, entry );
881
882 if( m_dialog
883 && ( aPackage.versions[0].runtime.value_or( PCM_PACKAGE_RUNTIME::PPR_SWIG ) == PCM_PACKAGE_RUNTIME::PPR_IPC )
884 && !Pgm().GetCommonSettings()->m_Api.enable_server )
885 {
886 // Defer the prompt until after installation completes
887 // to avoid UI operations during wxSafeYield
889 }
890}
891
892
894{
895 m_installed.erase( aPackage.identifier );
896}
897
898
900 const wxString& aPackageId )
901{
902 bool installed = m_installed.find( aPackageId ) != m_installed.end();
903
904 if( aRepositoryId.IsEmpty() || !CacheRepository( aRepositoryId ) )
905 return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
906
907 const PCM_REPOSITORY* repo;
908
909 try
910 {
911 repo = &getCachedRepository( aRepositoryId );
912 }
913 catch( ... )
914 {
915 return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
916 }
917
918 if( repo->package_map.count( aPackageId ) == 0 )
919 return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
920
921 const PCM_PACKAGE& pkg = repo->package_list[repo->package_map.at( aPackageId )];
922
923 if( installed )
924 {
925 // Package is installed, check for available updates at the same or
926 // higher (numerically lower) version stability level
927 wxString update_version = GetPackageUpdateVersion( pkg );
928
929 return update_version.IsEmpty() ? PPS_INSTALLED : PPS_UPDATE_AVAILABLE;
930 }
931 else
932 {
933 // Find any compatible version
934 auto ver_it = std::find_if( pkg.versions.begin(), pkg.versions.end(),
935 []( const PACKAGE_VERSION& ver )
936 {
937 return ver.compatible;
938 } );
939
940 return ver_it == pkg.versions.end() ? PPS_UNAVAILABLE : PPS_AVAILABLE;
941 }
942}
943
944
946 const wxString& aPackageId, const wxString& aRecordedName,
947 const STRING_TUPLE_LIST& aRepositoryList,
948 const std::unordered_map<wxString, PCM_REPOSITORY>& aCache )
949{
950 std::vector<wxString> publishers;
951 std::vector<wxString> nameMatches;
952
953 for( const auto& [id, name, url] : aRepositoryList )
954 {
955 auto cached = aCache.find( id );
956
957 if( cached == aCache.end() || cached->second.package_map.count( aPackageId ) == 0 )
958 continue;
959
960 publishers.push_back( id );
961
962 // MarkInstalled records the name a repository gives itself, so the recorded name has
963 // to be compared against that and not against the locally configured alias
964 if( cached->second.name == aRecordedName )
965 nameMatches.push_back( id );
966 }
967
968 if( nameMatches.size() == 1 )
969 return nameMatches.front();
970
971 // Choosing between several publishers by list order would hand the package to a source
972 // the user never picked
973 if( publishers.size() == 1 )
974 return publishers.front();
975
976 return wxString();
977}
978
979
981{
982 // An id that still names a configured repository is authoritative. Widening the search
983 // because its download happened to fail would hand the package to another publisher
984 if( findRepository( aEntry.repository_id ) != m_repository_list.end() )
985 {
986 if( !CacheRepository( aEntry.repository_id ) )
987 return false;
988
989 return getCachedRepository( aEntry.repository_id )
990 .package_map.count( aEntry.package.identifier ) > 0;
991 }
992
993 for( const auto& [id, name, url] : m_repository_list )
994 CacheRepository( id );
995
996 wxString resolved = ResolveRepositoryId( aEntry.package.identifier, aEntry.repository_name,
998
999 if( resolved.IsEmpty() )
1000 {
1001 wxLogTrace( tracePcm, wxS( "Package %s is not published by any configured repository" ),
1002 aEntry.package.identifier );
1003 return false;
1004 }
1005
1006 wxLogTrace( tracePcm, wxS( "Package %s repository id repaired from '%s' to '%s'" ),
1007 aEntry.package.identifier, aEntry.repository_id, resolved );
1008
1009 aEntry.repository_id = resolved;
1010 aEntry.repository_name = getCachedRepository( resolved ).name;
1011
1012 // The entry was skipped by every metadata refresh done under its stale id
1014
1015 return true;
1016}
1017
1018
1020{
1021 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
1022 resolveInstalledPackageRepository( pair.second );
1023}
1024
1025
1027{
1028 wxASSERT_MSG( m_installed.find( aPackage.identifier ) != m_installed.end(),
1029 wxT( "GetPackageUpdateVersion called on a not installed package" ) );
1030
1031 const PCM_INSTALLATION_ENTRY& entry = m_installed.at( aPackage.identifier );
1032
1033 auto installed_ver_it = std::find_if(
1034 entry.package.versions.begin(), entry.package.versions.end(),
1035 [&]( const PACKAGE_VERSION& ver )
1036 {
1037 return ver.version == entry.current_version;
1038 } );
1039
1040 wxCHECK_MSG( installed_ver_it != entry.package.versions.end(), wxEmptyString,
1041 wxT( "Installed package version not found" ) );
1042
1043 auto ver_it = std::find_if( aPackage.versions.begin(), aPackage.versions.end(),
1044 [&]( const PACKAGE_VERSION& ver )
1045 {
1046 return ver.compatible
1047 && installed_ver_it->status >= ver.status
1048 && installed_ver_it->parsed_version < ver.parsed_version;
1049 } );
1050
1051 return ver_it == aPackage.versions.end() ? wxString( wxT( "" ) ) : ver_it->version;
1052}
1053
1055{
1056 return std::chrono::duration_cast<std::chrono::seconds>(
1057 std::chrono::system_clock::now().time_since_epoch() ).count();
1058}
1059
1060
1062{
1063 try
1064 {
1065 nlohmann::json js;
1066 js["packages"] = nlohmann::json::array();
1067
1068 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
1069 {
1070 js["packages"].emplace_back( pair.second );
1071 }
1072
1073 wxFileName f( PATHS::GetUserSettingsPath(), wxT( "installed_packages.json" ) );
1074 std::ofstream stream( f.GetFullPath().fn_str() );
1075
1076 stream << std::setw( 4 ) << js << std::endl;
1077 }
1078 catch( nlohmann::detail::exception& )
1079 {
1080 // Ignore
1081 }
1082}
1083
1084
1085const std::vector<PCM_INSTALLATION_ENTRY> PLUGIN_CONTENT_MANAGER::GetInstalledPackages() const
1086{
1087 std::vector<PCM_INSTALLATION_ENTRY> v;
1088
1089 std::for_each( m_installed.begin(), m_installed.end(),
1090 [&v]( const std::pair<const wxString, PCM_INSTALLATION_ENTRY>& entry )
1091 {
1092 v.push_back( entry.second );
1093 } );
1094
1095 std::sort( v.begin(), v.end(),
1096 []( const PCM_INSTALLATION_ENTRY& a, const PCM_INSTALLATION_ENTRY& b )
1097 {
1098 return ( a.install_timestamp < b.install_timestamp )
1099 || ( a.install_timestamp == b.install_timestamp
1100 && a.package.identifier < b.package.identifier );
1101 } );
1102
1103 return v;
1104}
1105
1106
1107const wxString&
1109{
1110 wxASSERT_MSG( m_installed.find( aPackageId ) != m_installed.end(),
1111 wxT( "Installed package not found." ) );
1112
1113 return m_installed.at( aPackageId ).current_version;
1114}
1115
1116
1117bool PLUGIN_CONTENT_MANAGER::IsPackagePinned( const wxString& aPackageId ) const
1118{
1119 if( m_installed.find( aPackageId ) == m_installed.end() )
1120 return false;
1121
1122 return m_installed.at( aPackageId ).pinned;
1123}
1124
1125
1126void PLUGIN_CONTENT_MANAGER::SetPinned( const wxString& aPackageId, const bool aPinned )
1127{
1128 if( m_installed.find( aPackageId ) == m_installed.end() )
1129 return;
1130
1131 m_installed.at( aPackageId ).pinned = aPinned;
1132}
1133
1134
1136 const wxString& aSearchTerm )
1137{
1138 wxArrayString terms = wxStringTokenize( aSearchTerm.Lower(), wxS( " " ), wxTOKEN_STRTOK );
1139 int rank = 0;
1140
1141 const auto find_term_matches =
1142 [&]( const wxString& str )
1143 {
1144 int result = 0;
1145 wxString lower = str.Lower();
1146
1147 for( const wxString& term : terms )
1148 {
1149 if( lower.Find( term ) != wxNOT_FOUND )
1150 result += 1;
1151 }
1152
1153 return result;
1154 };
1155
1156 // Match on package id
1157 if( terms.size() == 1 && terms[0] == aPackage.identifier )
1158 rank += 10000;
1159
1160 if( terms.size() == 1 && find_term_matches( aPackage.identifier ) )
1161 rank += 1000;
1162
1163 // Match on package name
1164 rank += 500 * find_term_matches( aPackage.name );
1165
1166 // Match on tags
1167 for( const std::string& tag : aPackage.tags )
1168 rank += 100 * find_term_matches( wxString( tag ) );
1169
1170 // Match on package description
1171 rank += 10 * find_term_matches( aPackage.description );
1172 rank += 10 * find_term_matches( aPackage.description_full );
1173
1174 // Match on author/maintainer
1175 rank += find_term_matches( aPackage.author.name );
1176
1177 if( aPackage.maintainer )
1178 rank += 3 * find_term_matches( aPackage.maintainer->name );
1179
1180 // Match on resources
1181 for( const std::pair<const std::string, wxString>& entry : aPackage.resources )
1182 {
1183 rank += find_term_matches( entry.first );
1184 rank += find_term_matches( entry.second );
1185 }
1186
1187 // Match on license
1188 if( terms.size() == 1 && terms[0] == aPackage.license )
1189 rank += 1;
1190
1191 return rank;
1192}
1193
1194
1195std::unordered_map<wxString, wxBitmap>
1197{
1198 std::unordered_map<wxString, wxBitmap> bitmaps;
1199
1200 wxFileName resources_file = wxFileName( PATHS::GetUserCachePath(), wxT( "resources.zip" ) );
1201 resources_file.AppendDir( wxT( "pcm" ) );
1202 resources_file.AppendDir( aRepositoryId );
1203
1204 if( !resources_file.FileExists() )
1205 return bitmaps;
1206
1207 wxFFileInputStream stream( resources_file.GetFullPath() );
1208 wxZipInputStream zip( stream );
1209
1210 if( !zip.IsOk() || zip.GetTotalEntries() == 0 )
1211 return bitmaps;
1212
1213 for( wxArchiveEntry* entry = zip.GetNextEntry(); entry; entry = zip.GetNextEntry() )
1214 {
1215 wxArrayString path_parts = wxSplit( entry->GetName(), wxFileName::GetPathSeparator(),
1216 (wxChar) 0 );
1217
1218 if( path_parts.size() != 2 || path_parts[1] != wxT( "icon.png" ) )
1219 continue;
1220
1221 try
1222 {
1223 wxMemoryInputStream image_stream( zip, entry->GetSize() );
1224 wxImage image( image_stream, wxBITMAP_TYPE_PNG );
1225 bitmaps.emplace( path_parts[0], wxBitmap( image ) );
1226 }
1227 catch( ... )
1228 {
1229 // Log and ignore
1230 wxLogTrace( wxT( "Error loading png bitmap for entry %s from %s" ), entry->GetName(),
1231 resources_file.GetFullPath() );
1232 }
1233 }
1234
1235 return bitmaps;
1236}
1237
1238
1239std::unordered_map<wxString, wxBitmap> PLUGIN_CONTENT_MANAGER::GetInstalledPackageBitmaps()
1240{
1241 std::unordered_map<wxString, wxBitmap> bitmaps;
1242
1243 wxFileName resources_dir_fn( m_3rdparty_path, wxEmptyString );
1244 resources_dir_fn.AppendDir( wxT( "resources" ) );
1245 wxDir resources_dir( resources_dir_fn.GetPath() );
1246
1247 if( !resources_dir.IsOpened() )
1248 return bitmaps;
1249
1250 wxString subdir;
1251 bool more = resources_dir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN );
1252
1253 while( more )
1254 {
1255 wxFileName icon( resources_dir_fn.GetPath(), wxT( "icon.png" ) );
1256 icon.AppendDir( subdir );
1257
1258 if( icon.FileExists() )
1259 {
1260 wxString actual_package_id = subdir;
1261 actual_package_id.Replace( '_', '.' );
1262
1263 try
1264 {
1265 wxBitmap bitmap( icon.GetFullPath(), wxBITMAP_TYPE_PNG );
1266 bitmaps.emplace( actual_package_id, bitmap );
1267 }
1268 catch( ... )
1269 {
1270 // Log and ignore
1271 wxLogTrace( wxT( "Error loading png bitmap from %s" ), icon.GetFullPath() );
1272 }
1273 }
1274
1275 more = resources_dir.GetNext( &subdir );
1276 }
1277
1278 return bitmaps;
1279}
1280
1281
1283{
1284 UPDATE_CANCELLER( std::shared_ptr<BACKGROUND_JOB>& aJob ) : m_jobToCancel( aJob ) {};
1286 {
1287 if( m_jobToCancel )
1288 {
1290 m_jobToCancel.reset();
1291 }
1292 }
1293
1294 std::shared_ptr<BACKGROUND_JOB>& m_jobToCancel;
1295};
1296
1297
1299{
1300 // If the thread is already running don't create it again
1301 if( m_updateThread.joinable() )
1302 return;
1303
1304 m_updateBackgroundJob = Pgm().GetBackgroundJobMonitor().Create( _( "PCM Update" ) );
1305
1306 m_updateThread = std::thread(
1307 [this]()
1308 {
1310
1311 if( m_installed.size() == 0 )
1312 return;
1313
1314 int maxProgress = m_repository_list.size() + m_installed.size();
1315 m_updateBackgroundJob->m_reporter->SetNumPhases( maxProgress );
1316 m_updateBackgroundJob->m_reporter->Report( _( "Preparing to fetch repositories" ) );
1317
1318 // Only fetch repositories that have installed not pinned packages
1319 std::unordered_set<wxString> repo_ids;
1320
1321 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
1322 {
1323 if( !pair.second.pinned )
1324 repo_ids.insert( pair.second.repository_id );
1325 }
1326
1327 for( const auto& [ repository_id, name, url ] : m_repository_list )
1328 {
1329 m_updateBackgroundJob->m_reporter->AdvancePhase();
1330 if( repo_ids.count( repository_id ) == 0 )
1331 continue;
1332
1333 m_updateBackgroundJob->m_reporter->Report(
1334 _( "Fetching repository..." ) );
1335 CacheRepository( repository_id );
1336
1337 if( m_updateBackgroundJob->m_reporter->IsCancelled() )
1338 break;
1339 }
1340
1341 if( m_updateBackgroundJob->m_reporter->IsCancelled() )
1342 return;
1343
1344 // Count packages with updates
1345 int availableUpdateCount = 0;
1346
1347 m_updateBackgroundJob->m_reporter->Report( _( "Reviewing packages..." ) );
1348 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
1349 {
1350 PCM_INSTALLATION_ENTRY& entry = pair.second;
1351
1352 m_updateBackgroundJob->m_reporter->AdvancePhase();
1353
1354 if( !entry.pinned && resolveInstalledPackageRepository( entry ) )
1355 {
1357 entry.package.identifier );
1358
1359 if( state == PPS_UPDATE_AVAILABLE )
1360 availableUpdateCount++;
1361 }
1362
1363 if( m_updateBackgroundJob->m_reporter->IsCancelled() )
1364 return;
1365 }
1366
1367 // Update the badge on PCM button
1368 m_availableUpdateCallback( availableUpdateCount );
1369 } );
1370}
1371
1372
1374{
1375 if( m_updateThread.joinable() )
1376 {
1378 m_updateBackgroundJob->m_reporter->Cancel();
1379
1380 m_updateThread.join();
1381 }
1382}
1383
1384
1386{
1387 // By the time object is being destroyed the thread should be
1388 // stopped already but just in case do it here too.
1390}
1391
1392
1394{
1396 return;
1397
1399
1400 if( m_dialog
1401 && wxMessageBox( _( "This plugin requires the KiCad API, which is currently "
1402 "disabled in preferences. Would you like to enable it?" ),
1403 _( "Enable KiCad API" ), wxICON_QUESTION | wxYES_NO, m_dialog )
1404 == wxYES )
1405 {
1407 m_dialog->ParentFrame()->Kiway().CommonSettingsChanged();
1408 }
1409}
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.
Code outside of kicommon must validate through this class rather than instantiating nlohmann::json_sc...
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:624
virtual COMMON_SETTINGS * GetCommonSettings() const
Definition pgm_base.cpp:546
virtual ENV_VAR_MAP & GetLocalEnvVariables() const
Definition pgm_base.cpp:792
virtual BACKGROUND_JOBS_MONITOR & GetBackgroundJobMonitor() const
Definition pgm_base.h:129
time_t getCurrentTimestamp() const
Returns current UTC timestamp.
Definition pcm.cpp:1054
const std::vector< PCM_PACKAGE > & GetRepositoryPackages(const wxString &aRepositoryId) const
Get the packages metadata from a previously cached repository.
Definition pcm.cpp:763
void SetRepositoryList(const STRING_PAIR_LIST &aRepositories)
Set list of repositories.
Definition pcm.cpp:778
DIALOG_PCM * m_dialog
Definition pcm.h:466
std::unique_ptr< JSON_SCHEMA_VALIDATOR > m_schema_v1_validator
Definition pcm.h:467
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 DiscardAllRepositoryCaches()
Definition pcm.cpp:826
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:471
bool m_apiEnablePromptNeeded
Definition pcm.h:481
const PCM_REPOSITORY & getCachedRepository(const wxString &aRepositoryId) const
Get the cached repository metadata.
Definition pcm.cpp:421
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:1135
void MarkUninstalled(const PCM_PACKAGE &aPackage)
Mark package as uninstalled.
Definition pcm.cpp:893
std::unique_ptr< JSON_SCHEMA_VALIDATOR > m_schema_v2_validator
Definition pcm.h:468
bool CacheRepository(const wxString &aRepositoryId)
Cache specified repository packages and other metadata.
Definition pcm.cpp:430
void SaveInstalledPackages()
Saves metadata of installed packages to disk.
Definition pcm.cpp:1061
const std::vector< PCM_INSTALLATION_ENTRY > GetInstalledPackages() const
Get list of installed packages.
Definition pcm.cpp:1085
wxString m_3rdparty_path
Definition pcm.h:469
void SetPinned(const wxString &aPackageId, const bool aPinned)
Set the pinned status of a package.
Definition pcm.cpp:1126
static void PreparePackage(PCM_PACKAGE &aPackage)
Parses version strings and calculates compatibility.
Definition pcm.cpp:663
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:478
PCM_PACKAGE_STATE GetPackageState(const wxString &aRepositoryId, const wxString &aPackageId)
Get current state of the package.
Definition pcm.cpp:899
STRING_TUPLE_LIST::const_iterator findRepository(const wxString &aRepositoryId) const
Find a configured repository by id.
Definition pcm.cpp:410
std::map< wxString, PCM_INSTALLATION_ENTRY > m_installed
Definition pcm.h:475
void RunBackgroundUpdate()
Runs a background update thread that checks for new package versions.
Definition pcm.cpp:1298
std::unordered_map< wxString, wxBitmap > GetRepositoryPackageBitmaps(const wxString &aRepositoryId)
Get the icon bitmaps for repository packages.
Definition pcm.cpp:1196
const wxString GetPackageUpdateVersion(const PCM_PACKAGE &aPackage)
Get the preferred package update version or empty string if there is none.
Definition pcm.cpp:1026
void MarkInstalled(const PCM_PACKAGE &aPackage, const wxString &aVersion, const wxString &aRepositoryId)
Mark package as installed.
Definition pcm.cpp:848
static bool UsesSWIGRuntime(const PCM_PACKAGE &aPackage, const wxString &aVersion)
Returns true if the selected package version requires SWIG.
Definition pcm.cpp:744
std::unordered_map< wxString, wxBitmap > GetInstalledPackageBitmaps()
Get the icon bitmaps for installed packages.
Definition pcm.cpp:1239
const wxString & GetInstalledPackageVersion(const wxString &aPackageId) const
Get the current version of an installed package.
Definition pcm.cpp:1108
void updateInstalledPackagesMetadata(const wxString &aRepositoryId)
Updates metadata of installed packages from freshly fetched repo.
Definition pcm.cpp:599
bool IsPackagePinned(const wxString &aPackageId) const
Returns pinned status of a package.
Definition pcm.cpp:1117
static const std::tuple< int, int, int > m_kicad_version
Definition pcm.h:476
std::shared_ptr< BACKGROUND_JOB > m_updateBackgroundJob
Definition pcm.h:480
void StopBackgroundUpdate()
Interrupts and joins() the update thread.
Definition pcm.cpp:1373
static constexpr size_t DEFAULT_DOWNLOAD_MEM_LIMIT
Default download limit of 10 Mb to not use too much memory.
Definition pcm.h:411
STRING_TUPLE_LIST m_repository_list
Definition pcm.h:472
std::function< void(int)> m_availableUpdateCallback
Definition pcm.h:477
static wxString ResolveRepositoryId(const wxString &aPackageId, const wxString &aRecordedName, const STRING_TUPLE_LIST &aRepositoryList, const std::unordered_map< wxString, PCM_REPOSITORY > &aCache)
Find the cached repository that publishes a given package.
Definition pcm.cpp:945
bool FetchRepository(const wxString &aUrl, PCM_REPOSITORY &aRepository, PROGRESS_REPORTER *aReporter)
Fetches repository metadata from given url.
Definition pcm.cpp:270
void ResolveInstalledPackageRepositories()
Repair the repository id of every installed package.
Definition pcm.cpp:1019
void ShowApiEnablePromptIfNeeded()
Definition pcm.cpp:1393
void DiscardRepositoryCache(const wxString &aRepositoryId)
Discard in-memory and on-disk cache of a repository.
Definition pcm.cpp:807
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
bool resolveInstalledPackageRepository(PCM_INSTALLATION_ENTRY &aEntry)
Repair the repository id of a single installed package.
Definition pcm.cpp:980
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::string message
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:103
static const std::string PCM_ACCEPT_V2
Definition pcm.cpp:61
std::vector< std::pair< wxString, wxString > > STRING_PAIR_LIST
Definition pcm.h:79
const std::unordered_set< wxString > PCM_PACKAGE_DIRECTORIES({ "plugins", "footprints", "3dmodels", "symbols", "resources", "colors", "templates", "scripts", "design_blocks" })
Contains list of all valid directories that get extracted from a package archive.
PCM_PACKAGE_STATE
Package states Package is available if it is not installed and there is a compatible version Package ...
Definition pcm.h:59
@ PPS_INSTALLED
Definition pcm.h:62
@ PPS_UNAVAILABLE
Definition pcm.h:61
@ PPS_UPDATE_AVAILABLE
Definition pcm.h:65
@ PPS_AVAILABLE
Definition pcm.h:60
std::vector< std::tuple< wxString, wxString, wxString > > STRING_TUPLE_LIST
Definition pcm.h:80
@ 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 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
Package installation entry.
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
Package metadata.
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
Repository metadata.
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 reference to a resource.
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:1284
std::shared_ptr< BACKGROUND_JOB > & m_jobToCancel
Definition pcm.cpp:1294
JSON_SCHEMA_VALIDATOR validator(schema)
IbisParser parser & reporter
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:35
#define PR_CAN_ABORT