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