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