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