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 repo_cache.Rmdir( wxPATH_RMDIR_RECURSIVE );
818}
819
820
821void PLUGIN_CONTENT_MANAGER::MarkInstalled( const PCM_PACKAGE& aPackage, const wxString& aVersion,
822 const wxString& aRepositoryId )
823{
824 // In case of package update remove old data but keep pinned state
825 bool pinned = false;
826
827 if( m_installed.count( aPackage.identifier ) )
828 {
829 pinned = m_installed.at( aPackage.identifier ).pinned;
830 MarkUninstalled( aPackage );
831 }
832
834 entry.package = aPackage;
835 entry.current_version = aVersion;
836 entry.repository_id = aRepositoryId;
837
838 try
839 {
840 if( !aRepositoryId.IsEmpty() )
841 entry.repository_name = getCachedRepository( aRepositoryId ).name;
842 else
843 entry.repository_name = _( "Local file" );
844 }
845 catch( ... )
846 {
847 entry.repository_name = _( "Unknown" );
848 }
849
851 entry.pinned = pinned;
852
853 m_installed.emplace( aPackage.identifier, entry );
854
855 if( m_dialog
856 && ( aPackage.versions[0].runtime.value_or( PCM_PACKAGE_RUNTIME::PPR_SWIG ) == PCM_PACKAGE_RUNTIME::PPR_IPC )
857 && !Pgm().GetCommonSettings()->m_Api.enable_server )
858 {
859 // Defer the prompt until after installation completes
860 // to avoid UI operations during wxSafeYield
862 }
863}
864
865
867{
868 m_installed.erase( aPackage.identifier );
869}
870
871
873 const wxString& aPackageId )
874{
875 bool installed = m_installed.find( aPackageId ) != m_installed.end();
876
877 if( aRepositoryId.IsEmpty() || !CacheRepository( aRepositoryId ) )
878 return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
879
880 const PCM_REPOSITORY* repo;
881
882 try
883 {
884 repo = &getCachedRepository( aRepositoryId );
885 }
886 catch( ... )
887 {
888 return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
889 }
890
891 if( repo->package_map.count( aPackageId ) == 0 )
892 return installed ? PPS_INSTALLED : PPS_UNAVAILABLE;
893
894 const PCM_PACKAGE& pkg = repo->package_list[repo->package_map.at( aPackageId )];
895
896 if( installed )
897 {
898 // Package is installed, check for available updates at the same or
899 // higher (numerically lower) version stability level
900 wxString update_version = GetPackageUpdateVersion( pkg );
901
902 return update_version.IsEmpty() ? PPS_INSTALLED : PPS_UPDATE_AVAILABLE;
903 }
904 else
905 {
906 // Find any compatible version
907 auto ver_it = std::find_if( pkg.versions.begin(), pkg.versions.end(),
908 []( const PACKAGE_VERSION& ver )
909 {
910 return ver.compatible;
911 } );
912
913 return ver_it == pkg.versions.end() ? PPS_UNAVAILABLE : PPS_AVAILABLE;
914 }
915}
916
917
919 const wxString& aPackageId, const wxString& aRecordedName,
920 const STRING_TUPLE_LIST& aRepositoryList,
921 const std::unordered_map<wxString, PCM_REPOSITORY>& aCache )
922{
923 std::vector<wxString> publishers;
924 std::vector<wxString> nameMatches;
925
926 for( const auto& [id, name, url] : aRepositoryList )
927 {
928 auto cached = aCache.find( id );
929
930 if( cached == aCache.end() || cached->second.package_map.count( aPackageId ) == 0 )
931 continue;
932
933 publishers.push_back( id );
934
935 // MarkInstalled records the name a repository gives itself, so the recorded name has
936 // to be compared against that and not against the locally configured alias
937 if( cached->second.name == aRecordedName )
938 nameMatches.push_back( id );
939 }
940
941 if( nameMatches.size() == 1 )
942 return nameMatches.front();
943
944 // Choosing between several publishers by list order would hand the package to a source
945 // the user never picked
946 if( publishers.size() == 1 )
947 return publishers.front();
948
949 return wxString();
950}
951
952
954{
955 // An id that still names a configured repository is authoritative. Widening the search
956 // because its download happened to fail would hand the package to another publisher
957 if( findRepository( aEntry.repository_id ) != m_repository_list.end() )
958 {
959 if( !CacheRepository( aEntry.repository_id ) )
960 return false;
961
962 return getCachedRepository( aEntry.repository_id )
963 .package_map.count( aEntry.package.identifier ) > 0;
964 }
965
966 for( const auto& [id, name, url] : m_repository_list )
967 CacheRepository( id );
968
969 wxString resolved = ResolveRepositoryId( aEntry.package.identifier, aEntry.repository_name,
971
972 if( resolved.IsEmpty() )
973 {
974 wxLogTrace( tracePcm, wxS( "Package %s is not published by any configured repository" ),
975 aEntry.package.identifier );
976 return false;
977 }
978
979 wxLogTrace( tracePcm, wxS( "Package %s repository id repaired from '%s' to '%s'" ),
980 aEntry.package.identifier, aEntry.repository_id, resolved );
981
982 aEntry.repository_id = resolved;
983 aEntry.repository_name = getCachedRepository( resolved ).name;
984
985 // The entry was skipped by every metadata refresh done under its stale id
987
988 return true;
989}
990
991
993{
994 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
996}
997
998
1000{
1001 wxASSERT_MSG( m_installed.find( aPackage.identifier ) != m_installed.end(),
1002 wxT( "GetPackageUpdateVersion called on a not installed package" ) );
1003
1004 const PCM_INSTALLATION_ENTRY& entry = m_installed.at( aPackage.identifier );
1005
1006 auto installed_ver_it = std::find_if(
1007 entry.package.versions.begin(), entry.package.versions.end(),
1008 [&]( const PACKAGE_VERSION& ver )
1009 {
1010 return ver.version == entry.current_version;
1011 } );
1012
1013 wxCHECK_MSG( installed_ver_it != entry.package.versions.end(), wxEmptyString,
1014 wxT( "Installed package version not found" ) );
1015
1016 auto ver_it = std::find_if( aPackage.versions.begin(), aPackage.versions.end(),
1017 [&]( const PACKAGE_VERSION& ver )
1018 {
1019 return ver.compatible
1020 && installed_ver_it->status >= ver.status
1021 && installed_ver_it->parsed_version < ver.parsed_version;
1022 } );
1023
1024 return ver_it == aPackage.versions.end() ? wxString( wxT( "" ) ) : ver_it->version;
1025}
1026
1028{
1029 return std::chrono::duration_cast<std::chrono::seconds>(
1030 std::chrono::system_clock::now().time_since_epoch() ).count();
1031}
1032
1033
1035{
1036 try
1037 {
1038 nlohmann::json js;
1039 js["packages"] = nlohmann::json::array();
1040
1041 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
1042 {
1043 js["packages"].emplace_back( pair.second );
1044 }
1045
1046 wxFileName f( PATHS::GetUserSettingsPath(), wxT( "installed_packages.json" ) );
1047 std::ofstream stream( f.GetFullPath().fn_str() );
1048
1049 stream << std::setw( 4 ) << js << std::endl;
1050 }
1051 catch( nlohmann::detail::exception& )
1052 {
1053 // Ignore
1054 }
1055}
1056
1057
1058const std::vector<PCM_INSTALLATION_ENTRY> PLUGIN_CONTENT_MANAGER::GetInstalledPackages() const
1059{
1060 std::vector<PCM_INSTALLATION_ENTRY> v;
1061
1062 std::for_each( m_installed.begin(), m_installed.end(),
1063 [&v]( const std::pair<const wxString, PCM_INSTALLATION_ENTRY>& entry )
1064 {
1065 v.push_back( entry.second );
1066 } );
1067
1068 std::sort( v.begin(), v.end(),
1069 []( const PCM_INSTALLATION_ENTRY& a, const PCM_INSTALLATION_ENTRY& b )
1070 {
1071 return ( a.install_timestamp < b.install_timestamp )
1072 || ( a.install_timestamp == b.install_timestamp
1073 && a.package.identifier < b.package.identifier );
1074 } );
1075
1076 return v;
1077}
1078
1079
1080const wxString&
1082{
1083 wxASSERT_MSG( m_installed.find( aPackageId ) != m_installed.end(),
1084 wxT( "Installed package not found." ) );
1085
1086 return m_installed.at( aPackageId ).current_version;
1087}
1088
1089
1090bool PLUGIN_CONTENT_MANAGER::IsPackagePinned( const wxString& aPackageId ) const
1091{
1092 if( m_installed.find( aPackageId ) == m_installed.end() )
1093 return false;
1094
1095 return m_installed.at( aPackageId ).pinned;
1096}
1097
1098
1099void PLUGIN_CONTENT_MANAGER::SetPinned( const wxString& aPackageId, const bool aPinned )
1100{
1101 if( m_installed.find( aPackageId ) == m_installed.end() )
1102 return;
1103
1104 m_installed.at( aPackageId ).pinned = aPinned;
1105}
1106
1107
1109 const wxString& aSearchTerm )
1110{
1111 wxArrayString terms = wxStringTokenize( aSearchTerm.Lower(), wxS( " " ), wxTOKEN_STRTOK );
1112 int rank = 0;
1113
1114 const auto find_term_matches =
1115 [&]( const wxString& str )
1116 {
1117 int result = 0;
1118 wxString lower = str.Lower();
1119
1120 for( const wxString& term : terms )
1121 {
1122 if( lower.Find( term ) != wxNOT_FOUND )
1123 result += 1;
1124 }
1125
1126 return result;
1127 };
1128
1129 // Match on package id
1130 if( terms.size() == 1 && terms[0] == aPackage.identifier )
1131 rank += 10000;
1132
1133 if( terms.size() == 1 && find_term_matches( aPackage.identifier ) )
1134 rank += 1000;
1135
1136 // Match on package name
1137 rank += 500 * find_term_matches( aPackage.name );
1138
1139 // Match on tags
1140 for( const std::string& tag : aPackage.tags )
1141 rank += 100 * find_term_matches( wxString( tag ) );
1142
1143 // Match on package description
1144 rank += 10 * find_term_matches( aPackage.description );
1145 rank += 10 * find_term_matches( aPackage.description_full );
1146
1147 // Match on author/maintainer
1148 rank += find_term_matches( aPackage.author.name );
1149
1150 if( aPackage.maintainer )
1151 rank += 3 * find_term_matches( aPackage.maintainer->name );
1152
1153 // Match on resources
1154 for( const std::pair<const std::string, wxString>& entry : aPackage.resources )
1155 {
1156 rank += find_term_matches( entry.first );
1157 rank += find_term_matches( entry.second );
1158 }
1159
1160 // Match on license
1161 if( terms.size() == 1 && terms[0] == aPackage.license )
1162 rank += 1;
1163
1164 return rank;
1165}
1166
1167
1168std::unordered_map<wxString, wxBitmap>
1170{
1171 std::unordered_map<wxString, wxBitmap> bitmaps;
1172
1173 wxFileName resources_file = wxFileName( PATHS::GetUserCachePath(), wxT( "resources.zip" ) );
1174 resources_file.AppendDir( wxT( "pcm" ) );
1175 resources_file.AppendDir( aRepositoryId );
1176
1177 if( !resources_file.FileExists() )
1178 return bitmaps;
1179
1180 wxFFileInputStream stream( resources_file.GetFullPath() );
1181 wxZipInputStream zip( stream );
1182
1183 if( !zip.IsOk() || zip.GetTotalEntries() == 0 )
1184 return bitmaps;
1185
1186 for( wxArchiveEntry* entry = zip.GetNextEntry(); entry; entry = zip.GetNextEntry() )
1187 {
1188 wxArrayString path_parts = wxSplit( entry->GetName(), wxFileName::GetPathSeparator(),
1189 (wxChar) 0 );
1190
1191 if( path_parts.size() != 2 || path_parts[1] != wxT( "icon.png" ) )
1192 continue;
1193
1194 try
1195 {
1196 wxMemoryInputStream image_stream( zip, entry->GetSize() );
1197 wxImage image( image_stream, wxBITMAP_TYPE_PNG );
1198 bitmaps.emplace( path_parts[0], wxBitmap( image ) );
1199 }
1200 catch( ... )
1201 {
1202 // Log and ignore
1203 wxLogTrace( wxT( "Error loading png bitmap for entry %s from %s" ), entry->GetName(),
1204 resources_file.GetFullPath() );
1205 }
1206 }
1207
1208 return bitmaps;
1209}
1210
1211
1212std::unordered_map<wxString, wxBitmap> PLUGIN_CONTENT_MANAGER::GetInstalledPackageBitmaps()
1213{
1214 std::unordered_map<wxString, wxBitmap> bitmaps;
1215
1216 wxFileName resources_dir_fn( m_3rdparty_path, wxEmptyString );
1217 resources_dir_fn.AppendDir( wxT( "resources" ) );
1218 wxDir resources_dir( resources_dir_fn.GetPath() );
1219
1220 if( !resources_dir.IsOpened() )
1221 return bitmaps;
1222
1223 wxString subdir;
1224 bool more = resources_dir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN );
1225
1226 while( more )
1227 {
1228 wxFileName icon( resources_dir_fn.GetPath(), wxT( "icon.png" ) );
1229 icon.AppendDir( subdir );
1230
1231 if( icon.FileExists() )
1232 {
1233 wxString actual_package_id = subdir;
1234 actual_package_id.Replace( '_', '.' );
1235
1236 try
1237 {
1238 wxBitmap bitmap( icon.GetFullPath(), wxBITMAP_TYPE_PNG );
1239 bitmaps.emplace( actual_package_id, bitmap );
1240 }
1241 catch( ... )
1242 {
1243 // Log and ignore
1244 wxLogTrace( wxT( "Error loading png bitmap from %s" ), icon.GetFullPath() );
1245 }
1246 }
1247
1248 more = resources_dir.GetNext( &subdir );
1249 }
1250
1251 return bitmaps;
1252}
1253
1254
1256{
1257 UPDATE_CANCELLER( std::shared_ptr<BACKGROUND_JOB>& aJob ) : m_jobToCancel( aJob ) {};
1259 {
1260 if( m_jobToCancel )
1261 {
1263 m_jobToCancel.reset();
1264 }
1265 }
1266
1267 std::shared_ptr<BACKGROUND_JOB>& m_jobToCancel;
1268};
1269
1270
1272{
1273 // If the thread is already running don't create it again
1274 if( m_updateThread.joinable() )
1275 return;
1276
1277 m_updateBackgroundJob = Pgm().GetBackgroundJobMonitor().Create( _( "PCM Update" ) );
1278
1279 m_updateThread = std::thread(
1280 [this]()
1281 {
1283
1284 if( m_installed.size() == 0 )
1285 return;
1286
1287 int maxProgress = m_repository_list.size() + m_installed.size();
1288 m_updateBackgroundJob->m_reporter->SetNumPhases( maxProgress );
1289 m_updateBackgroundJob->m_reporter->Report( _( "Preparing to fetch repositories" ) );
1290
1291 // Only fetch repositories that have installed not pinned packages
1292 std::unordered_set<wxString> repo_ids;
1293
1294 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
1295 {
1296 if( !pair.second.pinned )
1297 repo_ids.insert( pair.second.repository_id );
1298 }
1299
1300 for( const auto& [ repository_id, name, url ] : m_repository_list )
1301 {
1302 m_updateBackgroundJob->m_reporter->AdvancePhase();
1303 if( repo_ids.count( repository_id ) == 0 )
1304 continue;
1305
1306 m_updateBackgroundJob->m_reporter->Report(
1307 _( "Fetching repository..." ) );
1308 CacheRepository( repository_id );
1309
1310 if( m_updateBackgroundJob->m_reporter->IsCancelled() )
1311 break;
1312 }
1313
1314 if( m_updateBackgroundJob->m_reporter->IsCancelled() )
1315 return;
1316
1317 // Count packages with updates
1318 int availableUpdateCount = 0;
1319
1320 m_updateBackgroundJob->m_reporter->Report( _( "Reviewing packages..." ) );
1321 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair : m_installed )
1322 {
1323 PCM_INSTALLATION_ENTRY& entry = pair.second;
1324
1325 m_updateBackgroundJob->m_reporter->AdvancePhase();
1326
1327 if( !entry.pinned && resolveInstalledPackageRepository( entry ) )
1328 {
1330 entry.package.identifier );
1331
1332 if( state == PPS_UPDATE_AVAILABLE )
1333 availableUpdateCount++;
1334 }
1335
1336 if( m_updateBackgroundJob->m_reporter->IsCancelled() )
1337 return;
1338 }
1339
1340 // Update the badge on PCM button
1341 m_availableUpdateCallback( availableUpdateCount );
1342 } );
1343}
1344
1345
1347{
1348 if( m_updateThread.joinable() )
1349 {
1351 m_updateBackgroundJob->m_reporter->Cancel();
1352
1353 m_updateThread.join();
1354 }
1355}
1356
1357
1359{
1360 // By the time object is being destroyed the thread should be
1361 // stopped already but just in case do it here too.
1363}
1364
1365
1367{
1369 return;
1370
1372
1373 if( m_dialog
1374 && wxMessageBox( _( "This plugin requires the KiCad API, which is currently "
1375 "disabled in preferences. Would you like to enable it?" ),
1376 _( "Enable KiCad API" ), wxICON_QUESTION | wxYES_NO, m_dialog )
1377 == wxYES )
1378 {
1380 m_dialog->ParentFrame()->Kiway().CommonSettingsChanged();
1381 }
1382}
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
Definition pcm.cpp:1027
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:463
std::unique_ptr< JSON_SCHEMA_VALIDATOR > m_schema_v1_validator
Definition pcm.h:464
bool DownloadToStream(const wxString &aUrl, std::ostream *aOutput, PROGRESS_REPORTER *aReporter, const size_t aSizeLimit=DEFAULT_DOWNLOAD_MEM_LIMIT, const std::string &aAccept="")
Downloads url to an output stream.
Definition pcm.cpp:202
void ValidateJson(const nlohmann::json &aJson, const nlohmann::json_uri &aUri=nlohmann::json_uri("#")) const
Validates json against a specific definition in the PCM schema.
Definition pcm.cpp:324
std::unordered_map< wxString, PCM_REPOSITORY > m_repository_cache
Definition pcm.h:468
bool m_apiEnablePromptNeeded
Definition pcm.h:477
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:1108
void MarkUninstalled(const PCM_PACKAGE &aPackage)
Mark package as uninstalled.
Definition pcm.cpp:866
std::unique_ptr< JSON_SCHEMA_VALIDATOR > m_schema_v2_validator
Definition pcm.h:465
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:1034
const std::vector< PCM_INSTALLATION_ENTRY > GetInstalledPackages() const
Get list of installed packages.
Definition pcm.cpp:1058
wxString m_3rdparty_path
Definition pcm.h:466
void SetPinned(const wxString &aPackageId, const bool aPinned)
Set the pinned status of a package.
Definition pcm.cpp:1099
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:474
PCM_PACKAGE_STATE GetPackageState(const wxString &aRepositoryId, const wxString &aPackageId)
Get current state of the package.
Definition pcm.cpp:872
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:471
void RunBackgroundUpdate()
Runs a background update thread that checks for new package versions.
Definition pcm.cpp:1271
std::unordered_map< wxString, wxBitmap > GetRepositoryPackageBitmaps(const wxString &aRepositoryId)
Get the icon bitmaps for repository packages.
Definition pcm.cpp:1169
const wxString GetPackageUpdateVersion(const PCM_PACKAGE &aPackage)
Get the preferred package update version or empty string if there is none.
Definition pcm.cpp:999
void MarkInstalled(const PCM_PACKAGE &aPackage, const wxString &aVersion, const wxString &aRepositoryId)
Mark package as installed.
Definition pcm.cpp:821
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:1212
const wxString & GetInstalledPackageVersion(const wxString &aPackageId) const
Get the current version of an installed package.
Definition pcm.cpp:1081
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:1090
static const std::tuple< int, int, int > m_kicad_version
Definition pcm.h:472
std::shared_ptr< BACKGROUND_JOB > m_updateBackgroundJob
Definition pcm.h:476
void StopBackgroundUpdate()
Interrupts and joins() the update thread.
Definition pcm.cpp:1346
static constexpr size_t DEFAULT_DOWNLOAD_MEM_LIMIT
< Default download limit of 10 Mb to not use too much memory
Definition pcm.h:409
STRING_TUPLE_LIST m_repository_list
Definition pcm.h:469
std::function< void(int)> m_availableUpdateCallback
Definition pcm.h:473
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:918
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:992
void ShowApiEnablePromptIfNeeded()
Definition pcm.cpp:1366
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:953
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:103
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
std::vector< std::tuple< wxString, wxString, wxString > > STRING_TUPLE_LIST
Definition pcm.h:79
@ PT_PLUGIN
Definition pcm_data.h:44
@ PT_FAB
Definition pcm_data.h:45
@ PVS_STABLE
Definition pcm_data.h:64
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
< Package version metadataPackage metadata
Definition pcm_data.h:92
wxString version
Definition pcm_data.h:93
PCM_PACKAGE_VERSION_STATUS status
Definition pcm_data.h:99
std::optional< wxString > kicad_version_max
Definition pcm_data.h:102
std::optional< int > version_epoch
Definition pcm_data.h:94
std::vector< std::string > platforms
Definition pcm_data.h:100
wxString kicad_version
Definition pcm_data.h:101
std::tuple< int, int, int, int > parsed_version
Definition pcm_data.h:107
wxString name
Definition pcm_data.h:81
Definition pcm_data.h:159
wxString repository_name
Definition pcm_data.h:163
PCM_PACKAGE package
Definition pcm_data.h:160
uint64_t install_timestamp
Definition pcm_data.h:164
wxString repository_id
Definition pcm_data.h:162
wxString current_version
Definition pcm_data.h:161
bool pinned
Definition pcm_data.h:165
Repository reference to a resource.
Definition pcm_data.h:114
wxString description
Definition pcm_data.h:116
wxString description_full
Definition pcm_data.h:117
wxString identifier
Definition pcm_data.h:118
wxString license
Definition pcm_data.h:123
std::vector< std::string > tags
Definition pcm_data.h:125
STRING_MAP resources
Definition pcm_data.h:124
std::optional< PCM_CONTACT > maintainer
Definition pcm_data.h:122
wxString name
Definition pcm_data.h:115
std::vector< PACKAGE_VERSION > versions
Definition pcm_data.h:127
PCM_PACKAGE_TYPE type
Definition pcm_data.h:119
PCM_CONTACT author
Definition pcm_data.h:121
Package installation entry.
Definition pcm_data.h:142
PCM_RESOURCE_REFERENCE packages
Definition pcm_data.h:144
std::vector< PCM_PACKAGE > package_list
Definition pcm_data.h:151
wxString name
Definition pcm_data.h:143
std::optional< PCM_RESOURCE_REFERENCE > resources
Definition pcm_data.h:145
std::unordered_map< wxString, size_t > package_map
Definition pcm_data.h:153
Repository metadata.
Definition pcm_data.h:133
std::optional< wxString > sha256
Definition pcm_data.h:135
uint64_t update_timestamp
Definition pcm_data.h:136
UPDATE_CANCELLER(std::shared_ptr< BACKGROUND_JOB > &aJob)
Definition pcm.cpp:1257
std::shared_ptr< BACKGROUND_JOB > & m_jobToCancel
Definition pcm.cpp:1267
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