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 );
 
  426        m_reporter->PCMReport( wxString::Format( 
_( 
"Removing previous version of package '%s'." ),
 
  433    if( 
extract( aFilePath, package.identifier, 
false ) )
 
  434        m_pcm->MarkInstalled( package, package.versions[0].version, 
"" );
 
 
  456    explicit PATH_COLLECTOR( std::vector<wxString>& aFiles, std::vector<wxString>& 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(
 
  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.
 
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)