33#include <unordered_set>
35#include <wx/filename.h>
37#include <wx/sstream.h>
38#include <wx/wfstream.h>
39#include <wx/zipstrm.h>
43 std::forward_list<wxRegEx>& aKeepOnUpdate )
45 auto compile_regex = [&](
const wxString& regex )
47 aKeepOnUpdate.emplace_front( regex, wxRE_DEFAULT );
49 if( !aKeepOnUpdate.front().IsValid() )
50 aKeepOnUpdate.pop_front();
59 const wxString& aRepositoryId,
const bool isUpdate )
64 file_path.AppendDir(
"pcm" );
65 file_path.SetFullName( wxString::Format(
"%s_v%s.zip", aPackage.
identifier, aVersion ) );
67 auto find_pkgver = std::find_if( aPackage.
versions.begin(), aPackage.
versions.end(),
70 return pv.version == aVersion;
73 if( find_pkgver == aPackage.
versions.end() )
75 m_reporter->PCMReport( wxString::Format(
_(
"Version %s of package %s not found!" ),
81 if( !wxDirExists( file_path.GetPath() )
82 && !wxFileName::Mkdir( file_path.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
84 m_reporter->PCMReport(
_(
"Unable to create download directory!" ),
89 int code =
downloadFile( file_path.GetFullPath(), *find_pkgver->download_url );
91 if( code != CURLE_OK )
94 wxRemoveFile( file_path.GetFullPath() );
98 PCM_TASK install_task = [aPackage, aVersion, aRepositoryId, file_path, isUpdate,
this]()
115 TRANSFER_CALLBACK callback = [&](
size_t dltotal,
size_t dlnow,
size_t ultotal,
size_t ulnow )
118 m_reporter->SetDownloadProgress( dlnow, dltotal );
125 std::ofstream out( aFilePath.ToUTF8(), std::ofstream::binary );
129 curl.
SetURL( url.ToUTF8().data() );
133 m_reporter->PCMReport( wxString::Format(
_(
"Downloading package url: '%s'" ), url ),
140 uint64_t download_total;
143 m_reporter->SetDownloadProgress( download_total, download_total );
145 if( code != CURLE_OK && code != CURLE_ABORTED_BY_CALLBACK )
147 m_reporter->PCMReport( wxString::Format(
_(
"Failed to download url %s\n%s" ), url,
157 const wxString& aVersion,
158 const wxString& aRepositoryId,
159 const wxFileName& aFilePath,
const bool isUpdate )
161 auto pkgver = std::find_if( aPackage.
versions.begin(), aPackage.
versions.end(),
164 return pv.version == aVersion;
167 if( pkgver == aPackage.
versions.end() )
169 m_reporter->PCMReport( wxString::Format(
_(
"Version %s of package %s not found!" ),
176 std::forward_list<wxRegEx> keep_on_update;
181 const std::optional<wxString>& hash = pkgver->download_sha256;
182 bool hash_match =
true;
186 std::ifstream stream( aFilePath.GetFullPath().fn_str(), std::ios::binary );
187 hash_match =
m_pcm->VerifyHash( stream, *hash );
192 m_reporter->PCMReport( wxString::Format(
_(
"Downloaded archive hash for package "
193 "%s does not match repository entry. "
194 "This may indicate a problem with the "
195 "package, if the issue persists "
196 "report this to repository maintainers." ),
199 wxRemoveFile( aFilePath.GetFullPath() );
207 wxString::Format(
_(
"Removing previous version of package '%s'." ),
215 wxString::Format(
_(
"Installing package '%s'." ), aPackage.
name ),
220 m_pcm->MarkInstalled( aPackage, pkgver->version, aRepositoryId );
232 wxRemoveFile( aFilePath.GetFullPath() );
238 bool isMultiThreaded )
240 wxFFileInputStream stream( aFilePath );
241 wxZipInputStream
zip( stream );
243 wxLogNull no_wx_logging;
245 int entries =
zip.GetTotalEntries();
248 wxArchiveEntry* entry =
zip.GetNextEntry();
257 wxString clean_package_id = aPackageId;
258 clean_package_id.Replace(
'.',
'_' );
260 for( ; entry; entry =
zip.GetNextEntry() )
262 wxArrayString path_parts;
266 m_reporter->PCMReport( wxString::Format(
_(
"Package archive entry '%s' would be extracted outside "
267 "of the package directory." ),
273 if( entry->IsDir() || path_parts.size() < 2
285 path_parts.Insert( clean_package_id, 1 );
292 m_reporter->PCMReport( wxString::Format(
_(
"Package archive entry '%s' would be extracted outside "
293 "of the package directory." ),
299 wxString fullname = target.GetFullPath();
302 wxString t_path = wxPathOnly( fullname );
304 if( !wxDirExists( t_path ) )
306 wxFileName::Mkdir( t_path, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
309 wxTempFileOutputStream out( fullname );
318 if(
const wxZipEntry* zipEntry =
dynamic_cast<const wxZipEntry*
>( entry ) )
320 int exec = zipEntry->GetMode() & ( wxPOSIX_USER_EXECUTE | wxPOSIX_GROUP_EXECUTE | wxPOSIX_OTHERS_EXECUTE );
324 if( exec && wxStat( fullname, &stat ) == 0 )
325 wxChmod( fullname, stat.st_mode | exec );
330 m_reporter->SetPackageProgress( extracted, entries );
332 if( !isMultiThreaded )
347 m_reporter->SetPackageProgress( entries, entries );
354 const wxString& aFilePath )
356 wxFFileInputStream stream( aFilePath );
360 wxLogError(
_(
"Could not open archive file." ) );
364 wxZipInputStream
zip( stream );
368 wxLogError(
_(
"Invalid archive file format." ) );
372 nlohmann::json metadata;
374 for( wxArchiveEntry* entry =
zip.GetNextEntry(); entry !=
nullptr; entry =
zip.GetNextEntry() )
377 if( entry->GetName() !=
"metadata.json" )
380 wxStringOutputStream strStream;
386 metadata = nlohmann::json::parse( strStream.GetString().ToUTF8().data() );
387 m_pcm->ValidateJson( metadata );
389 catch(
const std::exception& e )
391 wxLogError( wxString::Format(
_(
"Unable to parse package metadata:\n\n%s" ),
398 if( metadata.empty() )
400 wxLogError(
_(
"Archive does not contain a valid metadata.json file" ) );
407 if( package.versions.size() != 1 )
409 wxLogError(
_(
"Archive metadata must have a single version defined" ) );
413 if( !package.versions[0].compatible
414 && wxMessageBox(
_(
"This package version is incompatible with your KiCad version or "
415 "platform. Are you sure you want to install it anyway?" ),
416 _(
"Install package" ), wxICON_EXCLAMATION | wxYES_NO, aParent )
422 bool isUpdate =
false;
424 std::forward_list<wxRegEx> keep_on_update;
425 const std::vector<PCM_INSTALLATION_ENTRY> installed_packages =
m_pcm->GetInstalledPackages();
427 if( std::find_if( installed_packages.begin(), installed_packages.end(),
430 return entry.package.identifier == package.identifier;
432 != installed_packages.end() )
436 _(
"Package with identifier %s is already installed. "
437 "Would you like to update it to the version from selected file?" ),
438 package.identifier ),
439 _(
"Update package" ), wxICON_EXCLAMATION | wxYES_NO, aParent )
448 m_reporter = std::make_unique<DIALOG_PCM_PROGRESS>( aParent,
false );
457 m_reporter->PCMReport( wxString::Format(
_(
"Removing previous version of package '%s'." ),
464 const bool extracted =
extract( aFilePath, package.identifier,
false );
467 m_pcm->MarkInstalled( package, package.versions[0].version,
"" );
472 m_pcm->ShowApiEnablePromptIfNeeded();
495 explicit PATH_COLLECTOR( std::vector<wxString>& aFiles, std::vector<wxString>& aDirs ) :
500 wxDirTraverseResult
OnFile(
const wxString& aFilePath )
override
502 m_files.push_back( aFilePath );
503 return wxDIR_CONTINUE;
506 wxDirTraverseResult
OnDir(
const wxString& dirPath )
override
508 m_dirs.push_back( dirPath );
509 return wxDIR_CONTINUE;
515 const std::forward_list<wxRegEx>& aKeep )
518 wxString clean_package_id = aPackageId;
519 clean_package_id.Replace(
'.',
'_' );
521 int path_prefix_len =
m_pcm->Get3rdPartyPath().Length();
523 auto sort_func = [](
const wxString& a,
const wxString& b )
525 if( a.length() > b.length() )
527 if( a.length() < b.length() )
538 wxFileName d(
m_pcm->Get3rdPartyPath(),
"" );
540 d.AppendDir( clean_package_id );
545 m_reporter->PCMReport( wxString::Format(
_(
"Removing directory %s" ), d.GetPath() ),
550 if( !d.Rmdir( wxPATH_RMDIR_RECURSIVE ) )
553 wxString::Format(
_(
"Failed to remove directory %s" ), d.GetPath() ),
559 std::vector<wxString> files;
560 std::vector<wxString> dirs;
563 wxDir( d.GetFullPath() )
564 .Traverse( collector, wxEmptyString, wxDIR_DEFAULT | wxDIR_NO_FOLLOW );
567 std::sort( files.begin(), files.end(), sort_func );
568 std::sort( dirs.begin(), dirs.end(), sort_func );
571 for(
const wxString& file : files )
575 for(
const wxRegEx& re : aKeep )
577 wxString tmp = file.Mid( path_prefix_len );
578 tmp.Replace(
"\\",
"/" );
580 if( re.Matches( tmp ) )
588 wxRemoveFile( file );
592 for(
const wxString& empty_dir : dirs )
594 wxFileName dname( empty_dir,
"" );
608 m_pcm->MarkUninstalled( aPackage );
614 wxString::Format(
_(
"Package %s uninstalled" ), aPackage.
name ),
626 m_reporter = std::make_unique<DIALOG_PCM_PROGRESS>( aParent );
638 std::condition_variable condvar;
639 bool download_complete =
false;
641 int count_failed_tasks = 0;
642 int count_success_tasks = 0;
644 std::thread download_thread(
656 count_success_tasks++;
658 count_failed_tasks++;
662 condvar.notify_all();
665 std::unique_lock<std::mutex> lock( mutex );
666 download_complete =
true;
667 condvar.notify_all();
670 std::thread install_thread(
673 std::unique_lock<std::mutex> lock( mutex );
695 count_success_tasks++;
697 count_failed_tasks++;
707 if( count_failed_tasks != 0 )
710 wxString::Format(
_(
"%d out of %d operations failed." ), count_failed_tasks, count_tasks ),
715 if( count_success_tasks == count_tasks )
722 wxString::Format(
_(
"%d out of %d operations were initialized but not successful." ),
723 count_tasks - count_success_tasks, count_tasks ),
733 download_thread.join();
734 install_thread.join();
737 m_pcm->ShowApiEnablePromptIfNeeded();
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.
int GetTransferTotal(uint64_t &aDownloadedBytes) const
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 GetUserCachePath()
Gets the stock (install) 3d viewer plugins path.
PATH_COLLECTOR(std::vector< wxString > &aFiles, std::vector< wxString > &aDirs)
wxDirTraverseResult OnDir(const wxString &dirPath) override
std::vector< wxString > & m_dirs
std::vector< wxString > & m_files
wxDirTraverseResult OnFile(const wxString &aFilePath) override
void deletePackageDirectories(const wxString &aPackageId, const std::forward_list< wxRegEx > &aKeep={})
Delete all package files.
SYNC_QUEUE< PCM_TASK > m_install_queue
std::shared_ptr< PLUGIN_CONTENT_MANAGER > m_pcm
SYNC_QUEUE< PCM_TASK > m_download_queue
std::function< STATUS()> PCM_TASK
int downloadFile(const wxString &aFilePath, const wxString &aUrl)
Download URL to a file.
PCM_TASK_MANAGER::STATUS DownloadAndInstall(const PCM_PACKAGE &aPackage, const wxString &aVersion, const wxString &aRepositoryId, const bool isUpdate)
Enqueue package download and installation.
bool extract(const wxString &aFilePath, const wxString &aPackageId, bool isMultiThreaded)
Extract package archive.
void RunQueue(wxWindow *aParent)
Run queue of pending actions.
std::mutex m_changed_package_types_guard
PCM_TASK_MANAGER::STATUS installDownloadedPackage(const PCM_PACKAGE &aPackage, const wxString &aVersion, const wxString &aRepositoryId, const wxFileName &aFilePath, const bool isUpdate)
Installs downloaded package archive.
std::unique_ptr< DIALOG_PCM_PROGRESS > m_reporter
PCM_TASK_MANAGER::STATUS Uninstall(const PCM_PACKAGE &aPackage)
Enqueue package uninstallation.
PCM_TASK_MANAGER::STATUS InstallFromFile(wxWindow *aParent, const wxString &aFilePath)
Installs package from an archive file on disk.
std::unordered_set< PCM_PACKAGE_TYPE > m_changed_package_types
static void PreparePackage(PCM_PACKAGE &aPackage)
Parses version strings and calculates compatibility.
static bool ResolveArchiveEntryPath(const wxString &aDestDir, const wxString &aEntryName, wxFileName &aResult)
Resolve an untrusted archive entry name against the directory it is extracted into.
static bool SplitArchiveEntryName(const wxString &aEntryName, wxArrayString &aParts)
Split an untrusted archive entry name into its path components.
std::function< int(size_t, size_t, size_t, size_t)> TRANSFER_CALLBACK
Wrapper interface around the curl_easy API/.
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
void compile_keep_on_update_regex(const PCM_PACKAGE &pkg, const PACKAGE_VERSION &ver, std::forward_list< wxRegEx > &aKeepOnUpdate)
< Package version metadataPackage metadata
std::vector< std::string > keep_on_update
Repository reference to a resource.
std::vector< PACKAGE_VERSION > versions
std::vector< std::string > keep_on_update
static bool CopyStreamData(wxInputStream &inputStream, wxOutputStream &outputStream, wxFileOffset size)