32#include <unordered_set>
34#include <wx/filename.h>
36#include <wx/sstream.h>
37#include <wx/wfstream.h>
38#include <wx/zipstrm.h>
42 std::forward_list<wxRegEx>& aKeepOnUpdate )
44 auto compile_regex = [&](
const wxString& regex )
46 aKeepOnUpdate.emplace_front( regex, wxRE_DEFAULT );
48 if( !aKeepOnUpdate.front().IsValid() )
49 aKeepOnUpdate.pop_front();
58 const wxString& aRepositoryId,
const bool isUpdate )
63 file_path.AppendDir(
"pcm" );
64 file_path.SetFullName( wxString::Format(
"%s_v%s.zip", aPackage.
identifier, aVersion ) );
66 auto find_pkgver = std::find_if( aPackage.
versions.begin(), aPackage.
versions.end(),
69 return pv.version == aVersion;
72 if( find_pkgver == aPackage.
versions.end() )
74 m_reporter->PCMReport( wxString::Format(
_(
"Version %s of package %s not found!" ),
80 if( !wxDirExists( file_path.GetPath() )
81 && !wxFileName::Mkdir( file_path.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
83 m_reporter->PCMReport(
_(
"Unable to create download directory!" ),
88 int code =
downloadFile( file_path.GetFullPath(), *find_pkgver->download_url );
90 if( code != CURLE_OK )
93 wxRemoveFile( file_path.GetFullPath() );
97 PCM_TASK install_task = [aPackage, aVersion, aRepositoryId, file_path, isUpdate,
this]()
114 TRANSFER_CALLBACK callback = [&](
size_t dltotal,
size_t dlnow,
size_t ultotal,
size_t ulnow )
117 m_reporter->SetDownloadProgress( dlnow, dltotal );
124 std::ofstream out( aFilePath.ToUTF8(), std::ofstream::binary );
128 curl.
SetURL( url.ToUTF8().data() );
132 m_reporter->PCMReport( wxString::Format(
_(
"Downloading package url: '%s'" ), url ),
139 uint64_t download_total;
142 m_reporter->SetDownloadProgress( download_total, download_total );
144 if( code != CURLE_OK && code != CURLE_ABORTED_BY_CALLBACK )
146 m_reporter->PCMReport( wxString::Format(
_(
"Failed to download url %s\n%s" ), url,
156 const wxString& aVersion,
157 const wxString& aRepositoryId,
158 const wxFileName& aFilePath,
const bool isUpdate )
160 auto pkgver = std::find_if( aPackage.
versions.begin(), aPackage.
versions.end(),
163 return pv.version == aVersion;
166 if( pkgver == aPackage.
versions.end() )
168 m_reporter->PCMReport( wxString::Format(
_(
"Version %s of package %s not found!" ),
175 std::forward_list<wxRegEx> keep_on_update;
180 const std::optional<wxString>& hash = pkgver->download_sha256;
181 bool hash_match =
true;
185 std::ifstream stream( aFilePath.GetFullPath().fn_str(), std::ios::binary );
186 hash_match =
m_pcm->VerifyHash( stream, *hash );
191 m_reporter->PCMReport( wxString::Format(
_(
"Downloaded archive hash for package "
192 "%s does not match repository entry. "
193 "This may indicate a problem with the "
194 "package, if the issue persists "
195 "report this to repository maintainers." ),
198 wxRemoveFile( aFilePath.GetFullPath() );
206 wxString::Format(
_(
"Removing previous version of package '%s'." ),
214 wxString::Format(
_(
"Installing package '%s'." ), aPackage.
name ),
219 m_pcm->MarkInstalled( aPackage, pkgver->version, aRepositoryId );
231 wxRemoveFile( aFilePath.GetFullPath() );
237 bool isMultiThreaded )
239 wxFFileInputStream stream( aFilePath );
240 wxZipInputStream
zip( stream );
242 wxLogNull no_wx_logging;
244 int entries =
zip.GetTotalEntries();
247 wxArchiveEntry* entry =
zip.GetNextEntry();
256 wxString clean_package_id = aPackageId;
257 clean_package_id.Replace(
'.',
'_' );
259 for( ; entry; entry =
zip.GetNextEntry() )
261 wxArrayString path_parts =
262 wxSplit( entry->GetName(), wxFileName::GetPathSeparator(), (wxChar) 0 );
264 if( path_parts.size() < 2
266 || path_parts[path_parts.size() - 1].IsEmpty() )
277 path_parts.Insert( clean_package_id, 1 );
278 path_parts.Insert(
m_pcm->Get3rdPartyPath(), 0 );
280 wxString fullname = wxJoin( path_parts, wxFileName::GetPathSeparator(), (wxChar) 0 );
283 wxString t_path = wxPathOnly( fullname );
285 if( !wxDirExists( t_path ) )
287 wxFileName::Mkdir( t_path, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
290 wxTempFileOutputStream out( fullname );
299 m_reporter->SetPackageProgress( extracted, entries );
301 if( !isMultiThreaded )
316 m_reporter->SetPackageProgress( entries, entries );
323 const wxString& aFilePath )
325 wxFFileInputStream stream( aFilePath );
329 wxLogError(
_(
"Could not open archive file." ) );
333 wxZipInputStream
zip( stream );
337 wxLogError(
_(
"Invalid archive file format." ) );
341 nlohmann::json metadata;
343 for( wxArchiveEntry* entry =
zip.GetNextEntry(); entry !=
nullptr; entry =
zip.GetNextEntry() )
346 if( entry->GetName() !=
"metadata.json" )
349 wxStringOutputStream strStream;
355 metadata = nlohmann::json::parse( strStream.GetString().ToUTF8().data() );
356 m_pcm->ValidateJson( metadata );
358 catch(
const std::exception& e )
360 wxLogError( wxString::Format(
_(
"Unable to parse package metadata:\n\n%s" ),
367 if( metadata.empty() )
369 wxLogError(
_(
"Archive does not contain a valid metadata.json file" ) );
376 if( package.versions.size() != 1 )
378 wxLogError(
_(
"Archive metadata must have a single version defined" ) );
382 if( !package.versions[0].compatible
383 && wxMessageBox(
_(
"This package version is incompatible with your KiCad version or "
384 "platform. Are you sure you want to install it anyway?" ),
385 _(
"Install package" ), wxICON_EXCLAMATION | wxYES_NO, aParent )
391 bool isUpdate =
false;
393 std::forward_list<wxRegEx> keep_on_update;
394 const std::vector<PCM_INSTALLATION_ENTRY> installed_packages = m_pcm->GetInstalledPackages();
396 if( std::find_if( installed_packages.begin(), installed_packages.end(),
399 return entry.package.identifier == package.identifier;
401 != installed_packages.end() )
405 _(
"Package with identifier %s is already installed. "
406 "Would you like to update it to the version from selected file?" ),
407 package.identifier ),
408 _(
"Update package" ), wxICON_EXCLAMATION | wxYES_NO, aParent )
417 m_reporter = std::make_unique<DIALOG_PCM_PROGRESS>( aParent,
false );
419 m_reporter->ShowWindowModal();
426 m_reporter->PCMReport( wxString::Format(
_(
"Removing previous version of package '%s'." ),
430 deletePackageDirectories( package.identifier, keep_on_update );
433 if( extract( aFilePath, package.identifier,
false ) )
434 m_pcm->MarkInstalled( package, package.versions[0].version,
"" );
436 m_reporter->SetFinished();
437 m_reporter->KeepRefreshing(
false );
438 m_reporter->Destroy();
443 std::unique_lock lock( m_changed_package_types_guard );
444 m_changed_package_types.insert( package.type );
456 explicit PATH_COLLECTOR( std::vector<wxString>& aFiles, std::vector<wxString>& aDirs ) :
457 m_files( aFiles ), m_dirs( aDirs )
461 wxDirTraverseResult
OnFile(
const wxString& aFilePath )
override
463 m_files.push_back( aFilePath );
464 return wxDIR_CONTINUE;
467 wxDirTraverseResult
OnDir(
const wxString& dirPath )
override
469 m_dirs.push_back( dirPath );
470 return wxDIR_CONTINUE;
476 const std::forward_list<wxRegEx>& aKeep )
479 wxString clean_package_id = aPackageId;
480 clean_package_id.Replace(
'.',
'_' );
482 int path_prefix_len =
m_pcm->Get3rdPartyPath().Length();
484 auto sort_func = [](
const wxString& a,
const wxString& b )
486 if( a.length() > b.length() )
488 if( a.length() < b.length() )
499 wxFileName d(
m_pcm->Get3rdPartyPath(),
"" );
501 d.AppendDir( clean_package_id );
506 m_reporter->PCMReport( wxString::Format(
_(
"Removing directory %s" ), d.GetPath() ),
511 if( !d.Rmdir( wxPATH_RMDIR_RECURSIVE ) )
514 wxString::Format(
_(
"Failed to remove directory %s" ), d.GetPath() ),
520 std::vector<wxString> files;
521 std::vector<wxString> dirs;
524 wxDir( d.GetFullPath() )
525 .Traverse( collector, wxEmptyString, wxDIR_DEFAULT | wxDIR_NO_FOLLOW );
528 std::sort( files.begin(), files.end(), sort_func );
529 std::sort( dirs.begin(), dirs.end(), sort_func );
532 for(
const wxString& file : files )
536 for(
const wxRegEx& re : aKeep )
538 wxString tmp = file.Mid( path_prefix_len );
539 tmp.Replace(
"\\",
"/" );
541 if( re.Matches( tmp ) )
549 wxRemoveFile( file );
553 for(
const wxString& empty_dir : dirs )
555 wxFileName dname( empty_dir,
"" );
569 m_pcm->MarkUninstalled( aPackage );
575 wxString::Format(
_(
"Package %s uninstalled" ), aPackage.
name ),
587 m_reporter = std::make_unique<DIALOG_PCM_PROGRESS>( aParent );
599 std::condition_variable condvar;
600 bool download_complete =
false;
602 int count_failed_tasks = 0;
603 int count_success_tasks = 0;
605 std::thread download_thread(
611 m_download_queue.pop( task );
613 condvar.notify_all();
616 std::unique_lock<std::mutex> lock( mutex );
617 download_complete =
true;
618 condvar.notify_all();
621 std::thread install_thread(
624 std::unique_lock<std::mutex> lock( mutex );
646 count_success_tasks++;
648 count_failed_tasks++;
658 if( count_failed_tasks != 0 )
661 wxString::Format(
_(
"%d out of %d operations failed." ), count_failed_tasks, count_tasks ),
666 if( count_success_tasks == count_tasks )
673 wxString::Format(
_(
"%d out of %d operations were initialized but not successful." ),
674 count_tasks - count_success_tasks, count_tasks ),
684 download_thread.join();
685 install_thread.join();
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.
bool pop(T &aReceiver)
Pop a value if the queue into the provided variable.
bool empty() const
Return true if the queue is empty.
size_t size() const
Return the size of the queue.
void push(T const &aValue)
Push a value onto the queue.
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 metadata Package 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)