48#include <wx/mstream.h>
49#include <wx/tokenzr.h>
50#include <wx/wfstream.h>
51#include <wx/zipstrm.h>
59static const wxChar
tracePcm[] = wxT(
"KICAD_PCM" );
61static const std::string
PCM_ACCEPT_V2 =
"application/vnd.kicad.pcm.v2+json";
70 void error(
const json::json_pointer& ptr,
const json& instance,
71 const std::string& message )
override
73 throw std::invalid_argument( std::string(
"At " ) + ptr.to_string() +
", value:\n"
74 + instance.dump() +
"\n" + message +
"\n" );
89 schema_v1_file.AppendDir( wxS(
"schemas" ) );
95 schema_v2_file.AppendDir( wxS(
"schemas" ) );
104 std::ifstream installed_stream( f.GetFullPath().fn_str() );
105 nlohmann::json installed;
109 installed_stream >> installed;
111 if( installed.contains(
"packages" ) && installed[
"packages"].is_array() )
113 for(
const auto& js_entry : installed[
"packages"] )
120 catch( std::exception& e )
122 wxLogError( wxString::Format(
_(
"Error loading installed packages list: %s" ),
136 wxDir package_dir( d.GetPath() );
138 if( !package_dir.IsOpened() )
142 bool more = package_dir.GetFirst( &subdir,
"", wxDIR_DIRS | wxDIR_HIDDEN );
146 wxString actual_package_id = subdir;
147 actual_package_id.Replace(
'_',
'.' );
152 wxFileName subdir_file( d.GetPath(), subdir );
156 int stat_code = wxStat( subdir_file.GetFullPath(), &stat );
176 more = package_dir.GetNext( &subdir );
183 [&]( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& entry )
185 PreparePackage( entry.second.package );
204 const size_t aSizeLimit,
205 const std::string& aAccept )
207 bool size_exceeded =
false;
209 TRANSFER_CALLBACK callback = [&](
size_t dltotal,
size_t dlnow,
size_t ultotal,
size_t ulnow )
211 if( aSizeLimit > 0 && ( dltotal > aSizeLimit || dlnow > aSizeLimit ) )
213 size_exceeded =
true;
222 aReporter->
Report( wxString::Format(
_(
"Downloading %lld/%lld kB" ), dlnow / 1000,
235 curl.
SetURL( aUrl.ToUTF8().data() );
239 if( !aAccept.empty() )
247 if( code != CURLE_OK )
251 if( code == CURLE_ABORTED_BY_CALLBACK && size_exceeded )
252 wxMessageBox(
_(
"Download is too large." ) );
253 else if( code != CURLE_ABORTED_BY_CALLBACK )
267 std::stringstream repository_stream;
269 aReporter->
SetTitle(
_(
"Fetching repository" ) );
277 nlohmann::json repository_json;
281 repository_stream >> repository_json;
283 int schema_version = 1;
285 if( repository_json.contains(
"schema_version" ) )
286 schema_version = repository_json[
"schema_version"].get<
int>();
288 if( schema_version >= 2 )
291 nlohmann::json_uri(
"#/definitions/Repository" ) );
296 nlohmann::json_uri(
"#/definitions/Repository" ) );
302 catch(
const std::exception& e )
306 wxLogError(
_(
"Unable to parse repository: %s" ), e.what() );
307 wxLogError(
_(
"The given repository URL does not look like a valid KiCad package "
308 "repository. Please double check the URL." ) );
319 const nlohmann::json_uri& aUri )
const
328 const nlohmann::json_uri& aUri )
const
331 aValidator.
Validate( aJson, error_handler, aUri );
336 const std::optional<wxString>& aHash,
337 std::vector<PCM_PACKAGE>& aPackages,
341 std::stringstream packages_stream;
343 aReporter->
SetTitle(
_(
"Fetching repository packages" ) );
349 wxLogError(
_(
"Unable to load repository packages url." ) );
354 std::istringstream isstream( packages_stream.str() );
356 if( aHash && !
VerifyHash( isstream, *aHash ) )
359 wxLogError(
_(
"Packages hash doesn't match. Repository may be corrupted." ) );
366 nlohmann::json packages_json = nlohmann::json::parse( packages_stream.str() );
372 nlohmann::json_uri(
"#/definitions/PackageArray" ) );
374 aPackages = packages_json[
"packages"].get<std::vector<PCM_PACKAGE>>();
376 catch( std::exception& e )
380 wxLogError( wxString::Format(
_(
"Unable to parse packages metadata:\n\n%s" ),
393 std::vector<unsigned char> bytes( picosha2::k_digest_size );
395 picosha2::hash256( std::istreambuf_iterator<char>( aStream ), std::istreambuf_iterator<char>(),
396 bytes.begin(), bytes.end() );
397 std::string hex_str = picosha2::bytes_to_hex_string( bytes.begin(), bytes.end() );
399 return aHash.compare( hex_str ) == 0;
407 wxT(
"Repository is not cached." ) );
418 const auto repository_tuple =
420 [&aRepositoryId](
const std::tuple<wxString, wxString, wxString>& t )
422 return std::get<0>( t ) == aRepositoryId;
428 wxString url = std::get<2>( *repository_tuple );
434 std::shared_ptr<PROGRESS_REPORTER> reporter;
444 bool packages_cache_exists =
false;
448 repo_cache.AppendDir( wxT(
"pcm" ) );
449 repo_cache.AppendDir( aRepositoryId );
450 wxFileName packages_cache( repo_cache.GetPath(), wxT(
"packages.json" ) );
452 if( repo_cache.FileExists() && packages_cache.FileExists() )
454 std::ifstream repo_stream( repo_cache.GetFullPath().fn_str() );
464 wxLogError(
_(
"Failed to parse locally stored repository.json." ) );
471 std::ifstream packages_cache_stream( packages_cache.GetFullPath().fn_str() );
475 packages_cache_stream >> js;
476 saved_repo.
package_list = js[
"packages"].get<std::vector<PCM_PACKAGE>>();
478 for(
size_t i = 0; i < saved_repo.
package_list.size(); i++ )
486 packages_cache_exists =
true;
492 wxLogError(
_(
"Packages cache for current repository is corrupted, it will "
493 "be redownloaded." ) );
499 if( !packages_cache_exists )
509 for(
size_t i = 0; i < current_repo.
package_list.size(); i++ )
515 repo_cache.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
517 std::ofstream repo_cache_stream( repo_cache.GetFullPath().fn_str() );
518 repo_cache_stream << std::setw( 4 ) << nlohmann::json( current_repo ) << std::endl;
520 std::ofstream packages_cache_stream( packages_cache.GetFullPath().fn_str() );
522 js[
"packages"] = nlohmann::json( current_repo.
package_list );
523 packages_cache_stream << std::setw( 4 ) << js << std::endl;
534 wxFileName resource_file( repo_cache.GetPath(), wxT(
"resources.zip" ) );
538 if( resource_file.FileExists() )
539 mtime = wxFileModificationTime( resource_file.GetFullPath() );
543 std::ofstream resources_stream( resource_file.GetFullPath().fn_str(),
544 std::ios_base::binary );
546 reporter->SetTitle(
_(
"Downloading resources" ) );
552 resources_stream.close();
556 std::ifstream read_stream( resource_file.GetFullPath().fn_str(),
557 std::ios_base::binary );
566 wxLogError(
_(
"Resources file hash doesn't match and will not be used. "
567 "Repository may be corrupted." ) );
570 wxRemoveFile( resource_file.GetFullPath() );
576 wxRemoveFile( resource_file.GetFullPath() );
597 wxLogTrace(
tracePcm, wxS(
"Invalid/Missing repository " ) + aRepositoryId );
601 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair :
m_installed )
613 std::optional<PACKAGE_VERSION> current_version;
615 auto current_version_it =
619 return version.version == entry.current_version;
623 current_version = *current_version_it;
633 return version.version == entry.current_version;
644 return a.parsed_version > b.parsed_version;
656 int epoch = 0, major = 0, minor = 0, patch = 0;
661 wxStringTokenizer version_tokenizer( ver.
version,
"." );
663 major = wxAtoi( version_tokenizer.GetNextToken() );
665 if( version_tokenizer.HasMoreTokens() )
666 minor = wxAtoi( version_tokenizer.GetNextToken() );
668 if( version_tokenizer.HasMoreTokens() )
669 patch = wxAtoi( version_tokenizer.GetNextToken() );
671 ver.
parsed_version = std::make_tuple( epoch, major, minor, patch );
676 auto parse_version_tuple =
677 [](
const wxString& version,
int deflt )
679 int ver_major = deflt;
680 int ver_minor = deflt;
681 int ver_patch = deflt;
683 wxStringTokenizer tokenizer( version,
"." );
685 ver_major = wxAtoi( tokenizer.GetNextToken() );
687 if( tokenizer.HasMoreTokens() )
688 ver_minor = wxAtoi( tokenizer.GetNextToken() );
690 if( tokenizer.HasMoreTokens() )
691 ver_patch = wxAtoi( tokenizer.GetNextToken() );
693 return std::tuple<int, int, int>( ver_major, ver_minor, ver_patch );
704 wxString platform = wxT(
"windows" );
705#elif defined( __APPLE__ )
706 wxString platform = wxT(
"macos" );
708 wxString platform = wxT(
"linux" );
723 return a.parsed_version > b.parsed_version;
728const std::vector<PCM_PACKAGE>&
731 static std::vector<PCM_PACKAGE>
empty{};
749 auto it = std::find_if( aRepositories.begin(), aRepositories.end(),
750 [&](
const auto& new_entry )
752 return new_entry.first == std::get<1>( entry );
755 if( it == aRepositories.end() )
764 for(
const std::pair<wxString, wxString>& repo : aRepositories )
766 std::string url_sha = picosha2::hash256_hex_string( repo.second );
767 m_repository_list.push_back( std::make_tuple( url_sha.substr( 0, 16 ), repo.first,
779 repo_cache.AppendDir( wxT(
"pcm" ) );
780 repo_cache.AppendDir( aRepositoryId );
782 if( repo_cache.DirExists() )
783 repo_cache.Rmdir( wxPATH_RMDIR_RECURSIVE );
788 const wxString& aRepositoryId )
806 if( !aRepositoryId.IsEmpty() )
823 && !
Pgm().GetCommonSettings()->m_Api.enable_server )
839 const wxString& aPackageId )
876 return ver.compatible;
887 wxT(
"GetPackageUpdateVersion called on a not installed package" ) );
891 auto installed_ver_it = std::find_if(
895 return ver.version == entry.current_version;
899 wxT(
"Installed package version not found" ) );
901 auto ver_it = std::find_if( aPackage.
versions.begin(), aPackage.
versions.end(),
904 return ver.compatible
905 && installed_ver_it->status >= ver.status
906 && installed_ver_it->parsed_version < ver.parsed_version;
909 return ver_it == aPackage.
versions.end() ? wxString( wxT(
"" ) ) : ver_it->version;
914 return std::chrono::duration_cast<std::chrono::seconds>(
915 std::chrono::system_clock::now().time_since_epoch() ).count();
924 js[
"packages"] = nlohmann::json::array();
926 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair :
m_installed )
928 js[
"packages"].emplace_back( pair.second );
932 std::ofstream stream( f.GetFullPath().fn_str() );
934 stream << std::setw( 4 ) << js << std::endl;
936 catch( nlohmann::detail::exception& )
945 std::vector<PCM_INSTALLATION_ENTRY> v;
948 [&v](
const std::pair<const wxString, PCM_INSTALLATION_ENTRY>& entry )
950 v.push_back( entry.second );
953 std::sort( v.begin(), v.end(),
956 return ( a.install_timestamp < b.install_timestamp )
957 || ( a.install_timestamp == b.install_timestamp
958 && a.package.identifier < b.package.identifier );
969 wxT(
"Installed package not found." ) );
971 return m_installed.at( aPackageId ).current_version;
994 const wxString& aSearchTerm )
996 wxArrayString terms = wxStringTokenize( aSearchTerm.Lower(), wxS(
" " ), wxTOKEN_STRTOK );
999 const auto find_term_matches =
1000 [&](
const wxString& str )
1003 wxString lower = str.Lower();
1005 for(
const wxString& term : terms )
1007 if( lower.Find( term ) != wxNOT_FOUND )
1015 if( terms.size() == 1 && terms[0] == aPackage.
identifier )
1018 if( terms.size() == 1 && find_term_matches( aPackage.
identifier ) )
1022 rank += 500 * find_term_matches( aPackage.
name );
1025 for(
const std::string& tag : aPackage.
tags )
1026 rank += 100 * find_term_matches( wxString( tag ) );
1029 rank += 10 * find_term_matches( aPackage.
description );
1033 rank += find_term_matches( aPackage.
author.
name );
1036 rank += 3 * find_term_matches( aPackage.
maintainer->name );
1039 for(
const std::pair<const std::string, wxString>& entry : aPackage.
resources )
1041 rank += find_term_matches( entry.first );
1042 rank += find_term_matches( entry.second );
1046 if( terms.size() == 1 && terms[0] == aPackage.
license )
1053std::unordered_map<wxString, wxBitmap>
1056 std::unordered_map<wxString, wxBitmap> bitmaps;
1059 resources_file.AppendDir( wxT(
"pcm" ) );
1060 resources_file.AppendDir( aRepositoryId );
1062 if( !resources_file.FileExists() )
1065 wxFFileInputStream stream( resources_file.GetFullPath() );
1066 wxZipInputStream
zip( stream );
1068 if( !
zip.IsOk() ||
zip.GetTotalEntries() == 0 )
1071 for( wxArchiveEntry* entry =
zip.GetNextEntry(); entry; entry =
zip.GetNextEntry() )
1073 wxArrayString path_parts = wxSplit( entry->GetName(), wxFileName::GetPathSeparator(),
1076 if( path_parts.size() != 2 || path_parts[1] != wxT(
"icon.png" ) )
1081 wxMemoryInputStream image_stream(
zip, entry->GetSize() );
1082 wxImage
image( image_stream, wxBITMAP_TYPE_PNG );
1083 bitmaps.emplace( path_parts[0], wxBitmap(
image ) );
1088 wxLogTrace( wxT(
"Error loading png bitmap for entry %s from %s" ), entry->GetName(),
1089 resources_file.GetFullPath() );
1099 std::unordered_map<wxString, wxBitmap> bitmaps;
1102 resources_dir_fn.AppendDir( wxT(
"resources" ) );
1103 wxDir resources_dir( resources_dir_fn.GetPath() );
1105 if( !resources_dir.IsOpened() )
1109 bool more = resources_dir.GetFirst( &subdir, wxEmptyString, wxDIR_DIRS | wxDIR_HIDDEN );
1113 wxFileName icon( resources_dir_fn.GetPath(), wxT(
"icon.png" ) );
1114 icon.AppendDir( subdir );
1116 if( icon.FileExists() )
1118 wxString actual_package_id = subdir;
1119 actual_package_id.Replace(
'_',
'.' );
1123 wxBitmap bitmap( icon.GetFullPath(), wxBITMAP_TYPE_PNG );
1124 bitmaps.emplace( actual_package_id, bitmap );
1129 wxLogTrace( wxT(
"Error loading png bitmap from %s" ), icon.GetFullPath() );
1133 more = resources_dir.GetNext( &subdir );
1177 std::unordered_set<wxString> repo_ids;
1179 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair :
m_installed )
1181 if( !pair.second.pinned )
1182 repo_ids.insert( pair.second.repository_id );
1188 if( repo_ids.count( repository_id ) == 0 )
1192 _(
"Fetching repository..." ) );
1203 int availableUpdateCount = 0;
1206 for( std::pair<const wxString, PCM_INSTALLATION_ENTRY>& pair :
m_installed )
1218 availableUpdateCount++;
1259 && wxMessageBox(
_(
"This plugin requires the KiCad API, which is currently "
1260 "disabled in preferences. Would you like to enable it?" ),
1261 _(
"Enable KiCad API" ), wxICON_QUESTION | wxYES_NO,
m_dialog )
1265 m_dialog->ParentFrame()->Kiway().CommonSettingsChanged();
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.
nlohmann::json Validate(const nlohmann::json &aJson, nlohmann::json_schema::error_handler &aErrorHandler, const nlohmann::json_uri &aInitialUri=nlohmann::json_uri("#")) const
int Perform()
Equivalent to curl_easy_perform.
void SetHeader(const std::string &aName, const std::string &aValue)
Set an arbitrary header for the HTTP(s) request.
bool SetTransferCallback(const TRANSFER_CALLBACK &aCallback, size_t aInterval)
bool SetURL(const std::string &aURL)
Set the request URL.
bool SetFollowRedirects(bool aFollow)
Enable the following of HTTP(s) and other redirects, by default curl does not follow redirects.
bool 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.
static wxString GetStockDataPath(bool aRespectRunFromBuildDir=true)
Gets the stock (install) data path, which is the base path for things like scripting,...
static wxString GetUserCachePath()
Gets the stock (install) 3d viewer plugins path.
static wxString GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
virtual COMMON_SETTINGS * GetCommonSettings() const
virtual ENV_VAR_MAP & GetLocalEnvVariables() const
virtual BACKGROUND_JOBS_MONITOR & GetBackgroundJobMonitor() const
time_t getCurrentTimestamp() const
const std::vector< PCM_PACKAGE > & GetRepositoryPackages(const wxString &aRepositoryId) const
Get the packages metadata from a previously cached repository.
void SetRepositoryList(const STRING_PAIR_LIST &aRepositories)
Set list of repositories.
std::unique_ptr< JSON_SCHEMA_VALIDATOR > m_schema_v1_validator
bool DownloadToStream(const wxString &aUrl, std::ostream *aOutput, PROGRESS_REPORTER *aReporter, const size_t aSizeLimit=DEFAULT_DOWNLOAD_MEM_LIMIT, const std::string &aAccept="")
Downloads url to an output stream.
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.
std::unordered_map< wxString, PCM_REPOSITORY > m_repository_cache
bool m_apiEnablePromptNeeded
const PCM_REPOSITORY & getCachedRepository(const wxString &aRepositoryId) const
Get the cached repository metadata.
int GetPackageSearchRank(const PCM_PACKAGE &aPackage, const wxString &aSearchTerm)
Get the approximate measure of how much given package matches the search term.
void MarkUninstalled(const PCM_PACKAGE &aPackage)
Mark package as uninstalled.
std::unique_ptr< JSON_SCHEMA_VALIDATOR > m_schema_v2_validator
bool CacheRepository(const wxString &aRepositoryId)
Cache specified repository packages and other metadata.
void SaveInstalledPackages()
Saves metadata of installed packages to disk.
~PLUGIN_CONTENT_MANAGER()
const std::vector< PCM_INSTALLATION_ENTRY > GetInstalledPackages() const
Get list of installed packages.
void SetPinned(const wxString &aPackageId, const bool aPinned)
Set the pinned status of a package.
static void PreparePackage(PCM_PACKAGE &aPackage)
Parses version strings and calculates compatibility.
PLUGIN_CONTENT_MANAGER(std::function< void(int)> aAvailableUpdateCallbac)
bool fetchPackages(const wxString &aUrl, const std::optional< wxString > &aHash, std::vector< PCM_PACKAGE > &aPackages, PROGRESS_REPORTER *aReporter, int aSchemaVersion=1)
Downloads packages metadata to in memory stream, verifies hash and attempts to parse it.
std::thread m_updateThread
PCM_PACKAGE_STATE GetPackageState(const wxString &aRepositoryId, const wxString &aPackageId)
Get current state of the package.
std::map< wxString, PCM_INSTALLATION_ENTRY > m_installed
void RunBackgroundUpdate()
Runs a background update thread that checks for new package versions.
std::unordered_map< wxString, wxBitmap > GetRepositoryPackageBitmaps(const wxString &aRepositoryId)
Get the icon bitmaps for repository packages.
const wxString GetPackageUpdateVersion(const PCM_PACKAGE &aPackage)
Get the preferred package update version or empty string if there is none.
void MarkInstalled(const PCM_PACKAGE &aPackage, const wxString &aVersion, const wxString &aRepositoryId)
Mark package as installed.
std::unordered_map< wxString, wxBitmap > GetInstalledPackageBitmaps()
Get the icon bitmaps for installed packages.
const wxString & GetInstalledPackageVersion(const wxString &aPackageId) const
Get the current version of an installed package.
void updateInstalledPackagesMetadata(const wxString &aRepositoryId)
Updates metadata of installed packages from freshly fetched repo.
bool IsPackagePinned(const wxString &aPackageId) const
Returns pinned status of a package.
static const std::tuple< int, int, int > m_kicad_version
std::shared_ptr< BACKGROUND_JOB > m_updateBackgroundJob
void StopBackgroundUpdate()
Interrupts and joins() the update thread.
static constexpr size_t DEFAULT_DOWNLOAD_MEM_LIMIT
< Default download limit of 10 Mb to not use too much memory
STRING_TUPLE_LIST m_repository_list
std::function< void(int)> m_availableUpdateCallback
bool FetchRepository(const wxString &aUrl, PCM_REPOSITORY &aRepository, PROGRESS_REPORTER *aReporter)
Fetches repository metadata from given url.
void ShowApiEnablePromptIfNeeded()
void DiscardRepositoryCache(const wxString &aRepositoryId)
Discard in-memory and on-disk cache of a repository.
bool VerifyHash(std::istream &aStream, const wxString &aHash) const
Verifies SHA256 hash of a binary stream.
void ReadEnvVar()
Stores 3rdparty path from environment variables.
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
static bool empty(const wxTextEntryBase *aCtrl)
Base window classes and related definitions.
Functions related to environment variables, including help functions.
static const wxChar tracePcm[]
Flag to enable PCM debugging output.
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.
static const std::string PCM_ACCEPT_V2
std::vector< std::pair< wxString, wxString > > STRING_PAIR_LIST
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
PGM_BASE & Pgm()
The global program "get" accessor.
< Package version metadataPackage metadata
PCM_PACKAGE_VERSION_STATUS status
std::optional< wxString > kicad_version_max
std::optional< int > version_epoch
std::vector< std::string > platforms
std::tuple< int, int, int, int > parsed_version
uint64_t install_timestamp
Repository reference to a resource.
wxString description_full
std::vector< std::string > tags
std::optional< PCM_CONTACT > maintainer
std::vector< PACKAGE_VERSION > versions
Package installation entry.
PCM_RESOURCE_REFERENCE packages
std::vector< PCM_PACKAGE > package_list
std::optional< PCM_RESOURCE_REFERENCE > resources
std::unordered_map< wxString, size_t > package_map
std::optional< wxString > sha256
uint64_t update_timestamp
UPDATE_CANCELLER(std::shared_ptr< BACKGROUND_JOB > &aJob)
std::shared_ptr< BACKGROUND_JOB > & m_jobToCancel
wxString result
Test unit parsing edge cases and error handling.
#define FN_NORMALIZE_FLAGS
Default flags to pass to wxFileName::Normalize().