KiCad PCB EDA Suite
Loading...
Searching...
No Matches
pcm_task_manager.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// kicad_curl_easy.h **must be** included before any wxWidgets header to avoid conflicts
21// at least on Windows/msys2
24
25#include <paths.h>
26#include "pcm_task_manager.h"
27#include <reporter.h>
28#include <wxstream_helper.h>
29
30#include <fstream>
31#include <thread>
32#include <unordered_set>
33#include <wx/dir.h>
34#include <wx/filename.h>
35#include <wx/msgdlg.h>
36#include <wx/sstream.h>
37#include <wx/wfstream.h>
38#include <wx/zipstrm.h>
39
40
42 std::forward_list<wxRegEx>& aKeepOnUpdate )
43{
44 auto compile_regex = [&]( const wxString& regex )
45 {
46 aKeepOnUpdate.emplace_front( regex, wxRE_DEFAULT );
47
48 if( !aKeepOnUpdate.front().IsValid() )
49 aKeepOnUpdate.pop_front();
50 };
51
52 std::for_each( pkg.keep_on_update.begin(), pkg.keep_on_update.end(), compile_regex );
53 std::for_each( ver.keep_on_update.begin(), ver.keep_on_update.end(), compile_regex );
54}
55
56
58 const wxString& aRepositoryId, const bool isUpdate )
59{
60 PCM_TASK download_task = [aPackage, aVersion, aRepositoryId, isUpdate, this]() -> PCM_TASK_MANAGER::STATUS
61 {
62 wxFileName file_path( PATHS::GetUserCachePath(), "" );
63 file_path.AppendDir( "pcm" );
64 file_path.SetFullName( wxString::Format( "%s_v%s.zip", aPackage.identifier, aVersion ) );
65
66 auto find_pkgver = std::find_if( aPackage.versions.begin(), aPackage.versions.end(),
67 [&aVersion]( const PACKAGE_VERSION& pv )
68 {
69 return pv.version == aVersion;
70 } );
71
72 if( find_pkgver == aPackage.versions.end() )
73 {
74 m_reporter->PCMReport( wxString::Format( _( "Version %s of package %s not found!" ),
75 aVersion, aPackage.identifier ),
78 }
79
80 if( !wxDirExists( file_path.GetPath() )
81 && !wxFileName::Mkdir( file_path.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
82 {
83 m_reporter->PCMReport( _( "Unable to create download directory!" ),
86 }
87
88 int code = downloadFile( file_path.GetFullPath(), *find_pkgver->download_url );
89
90 if( code != CURLE_OK )
91 {
92 // Cleanup after ourselves and exit
93 wxRemoveFile( file_path.GetFullPath() );
95 }
96
97 PCM_TASK install_task = [aPackage, aVersion, aRepositoryId, file_path, isUpdate, this]()
98 {
99 return installDownloadedPackage( aPackage, aVersion, aRepositoryId, file_path, isUpdate );
100 };
101
102 m_install_queue.push( install_task );
103
105 };
106
107 m_download_queue.push( download_task );
109}
110
111
112int PCM_TASK_MANAGER::downloadFile( const wxString& aFilePath, const wxString& url )
113{
114 TRANSFER_CALLBACK callback = [&]( size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow )
115 {
116 if( dltotal > 1024 )
117 m_reporter->SetDownloadProgress( dlnow, dltotal );
118 else
119 m_reporter->SetDownloadProgress( 0.0, 0.0 );
120
121 return m_reporter->IsCancelled();
122 };
123
124 std::ofstream out( aFilePath.ToUTF8(), std::ofstream::binary );
125
126 KICAD_CURL_EASY curl;
127 curl.SetOutputStream( &out );
128 curl.SetURL( url.ToUTF8().data() );
129 curl.SetFollowRedirects( true );
130 curl.SetTransferCallback( callback, 250000L );
131
132 m_reporter->PCMReport( wxString::Format( _( "Downloading package url: '%s'" ), url ),
134
135 int code = curl.Perform();
136
137 out.close();
138
139 uint64_t download_total;
140
141 if( CURLE_OK == curl.GetTransferTotal( download_total ) )
142 m_reporter->SetDownloadProgress( download_total, download_total );
143
144 if( code != CURLE_OK && code != CURLE_ABORTED_BY_CALLBACK )
145 {
146 m_reporter->PCMReport( wxString::Format( _( "Failed to download url %s\n%s" ), url,
147 curl.GetErrorText( code ) ),
149 }
150
151 return code;
152}
153
154
156 const wxString& aVersion,
157 const wxString& aRepositoryId,
158 const wxFileName& aFilePath, const bool isUpdate )
159{
160 auto pkgver = std::find_if( aPackage.versions.begin(), aPackage.versions.end(),
161 [&aVersion]( const PACKAGE_VERSION& pv )
162 {
163 return pv.version == aVersion;
164 } );
165
166 if( pkgver == aPackage.versions.end() )
167 {
168 m_reporter->PCMReport( wxString::Format( _( "Version %s of package %s not found!" ),
169 aVersion, aPackage.identifier ),
172 }
173
174 // wxRegEx is not CopyConstructible hence the weird choice of forward_list
175 std::forward_list<wxRegEx> keep_on_update;
176
177 if( isUpdate )
178 compile_keep_on_update_regex( aPackage, *pkgver, keep_on_update );
179
180 const std::optional<wxString>& hash = pkgver->download_sha256;
181 bool hash_match = true;
182
183 if( hash )
184 {
185 std::ifstream stream( aFilePath.GetFullPath().fn_str(), std::ios::binary );
186 hash_match = m_pcm->VerifyHash( stream, *hash );
187 }
188
189 if( !hash_match )
190 {
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." ),
196 aPackage.name ),
198 wxRemoveFile( aFilePath.GetFullPath() );
200 }
201 else
202 {
203 if( isUpdate )
204 {
205 m_reporter->PCMReport(
206 wxString::Format( _( "Removing previous version of package '%s'." ),
207 aPackage.name ),
209
210 deletePackageDirectories( aPackage.identifier, keep_on_update );
211 }
212
213 m_reporter->PCMReport(
214 wxString::Format( _( "Installing package '%s'." ), aPackage.name ),
216
217 if( extract( aFilePath.GetFullPath(), aPackage.identifier, true ) )
218 {
219 m_pcm->MarkInstalled( aPackage, pkgver->version, aRepositoryId );
220 }
221 else
222 {
223 // Cleanup possibly partially extracted package
225 }
226
227 std::unique_lock lock( m_changed_package_types_guard );
228 m_changed_package_types.insert( aPackage.type );
229 }
230
231 wxRemoveFile( aFilePath.GetFullPath() );
233}
234
235
236bool PCM_TASK_MANAGER::extract( const wxString& aFilePath, const wxString& aPackageId,
237 bool isMultiThreaded )
238{
239 wxFFileInputStream stream( aFilePath );
240 wxZipInputStream zip( stream );
241
242 wxLogNull no_wx_logging;
243
244 int entries = zip.GetTotalEntries();
245 int extracted = 0;
246
247 wxArchiveEntry* entry = zip.GetNextEntry();
248
249 if( !zip.IsOk() )
250 {
251 m_reporter->PCMReport( _( "Error extracting file!" ), RPT_SEVERITY_ERROR );
252 return false;
253 }
254
255 // Namespace delimiter changed on disk to allow flat loading of Python modules
256 wxString clean_package_id = aPackageId;
257 clean_package_id.Replace( '.', '_' );
258
259 for( ; entry; entry = zip.GetNextEntry() )
260 {
261 wxArrayString path_parts =
262 wxSplit( entry->GetName(), wxFileName::GetPathSeparator(), (wxChar) 0 );
263
264 if( path_parts.size() < 2
265 || PCM_PACKAGE_DIRECTORIES.find( path_parts[0] ) == PCM_PACKAGE_DIRECTORIES.end()
266 || path_parts[path_parts.size() - 1].IsEmpty() )
267 {
268 // Ignore files in the root of the archive and files outside of package dirs.
269 continue;
270 }
271
272
273 // Transform paths from
274 // <PackageRoot>/$folder/$contents
275 // To
276 // $KICAD7_3RD_PARTY/$folder/$package_id/$contents
277 path_parts.Insert( clean_package_id, 1 );
278 path_parts.Insert( m_pcm->Get3rdPartyPath(), 0 );
279
280 wxString fullname = wxJoin( path_parts, wxFileName::GetPathSeparator(), (wxChar) 0 );
281
282 // Ensure the target directory exists and create it if not.
283 wxString t_path = wxPathOnly( fullname );
284
285 if( !wxDirExists( t_path ) )
286 {
287 wxFileName::Mkdir( t_path, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
288 }
289
290 wxTempFileOutputStream out( fullname );
291
292 if( !( CopyStreamData( zip, out, entry->GetSize() ) && out.Commit() ) )
293 {
294 m_reporter->PCMReport( _( "Error extracting file!" ), RPT_SEVERITY_ERROR );
295 return false;
296 }
297
298#ifndef __WXMSW__
299 if( const wxZipEntry* zipEntry = dynamic_cast<const wxZipEntry*>( entry ) )
300 {
301 int exec = zipEntry->GetMode() & ( wxPOSIX_USER_EXECUTE | wxPOSIX_GROUP_EXECUTE | wxPOSIX_OTHERS_EXECUTE );
302
303 wxStructStat stat;
304
305 if( exec && wxStat( fullname, &stat ) == 0 )
306 wxChmod( fullname, stat.st_mode | exec );
307 }
308#endif
309
310 extracted++;
311 m_reporter->SetPackageProgress( extracted, entries );
312
313 if( !isMultiThreaded )
314 m_reporter->KeepRefreshing( false );
315
316 if( m_reporter->IsCancelled() )
317 break;
318 }
319
320 zip.CloseEntry();
321
322 if( m_reporter->IsCancelled() )
323 {
324 m_reporter->PCMReport( _( "Aborting package installation." ), RPT_SEVERITY_INFO );
325 return false;
326 }
327
328 m_reporter->SetPackageProgress( entries, entries );
329
330 return true;
331}
332
333
335 const wxString& aFilePath )
336{
337 wxFFileInputStream stream( aFilePath );
338
339 if( !stream.IsOk() )
340 {
341 wxLogError( _( "Could not open archive file." ) );
343 }
344
345 wxZipInputStream zip( stream );
346
347 if( !zip.IsOk() )
348 {
349 wxLogError( _( "Invalid archive file format." ) );
351 }
352
353 nlohmann::json metadata;
354
355 for( wxArchiveEntry* entry = zip.GetNextEntry(); entry != nullptr; entry = zip.GetNextEntry() )
356 {
357 // Find and load metadata.json
358 if( entry->GetName() != "metadata.json" )
359 continue;
360
361 wxStringOutputStream strStream;
362
363 if( CopyStreamData( zip, strStream, entry->GetSize() ) )
364 {
365 try
366 {
367 metadata = nlohmann::json::parse( strStream.GetString().ToUTF8().data() );
368 m_pcm->ValidateJson( metadata );
369 }
370 catch( const std::exception& e )
371 {
372 wxLogError( wxString::Format( _( "Unable to parse package metadata:\n\n%s" ),
373 e.what() ) );
374 break;
375 }
376 }
377 }
378
379 if( metadata.empty() )
380 {
381 wxLogError( _( "Archive does not contain a valid metadata.json file" ) );
383 }
384
385 PCM_PACKAGE package = metadata.get<PCM_PACKAGE>();
387
388 if( package.versions.size() != 1 )
389 {
390 wxLogError( _( "Archive metadata must have a single version defined" ) );
392 }
393
394 if( !package.versions[0].compatible
395 && wxMessageBox( _( "This package version is incompatible with your KiCad version or "
396 "platform. Are you sure you want to install it anyway?" ),
397 _( "Install package" ), wxICON_EXCLAMATION | wxYES_NO, aParent )
398 == wxNO )
399 {
401 }
402
403 bool isUpdate = false;
404 // wxRegEx is not CopyConstructible hence the weird choice of forward_list
405 std::forward_list<wxRegEx> keep_on_update;
406 const std::vector<PCM_INSTALLATION_ENTRY> installed_packages = m_pcm->GetInstalledPackages();
407
408 if( std::find_if( installed_packages.begin(), installed_packages.end(),
409 [&]( const PCM_INSTALLATION_ENTRY& entry )
410 {
411 return entry.package.identifier == package.identifier;
412 } )
413 != installed_packages.end() )
414 {
415 if( wxMessageBox(
416 wxString::Format(
417 _( "Package with identifier %s is already installed. "
418 "Would you like to update it to the version from selected file?" ),
419 package.identifier ),
420 _( "Update package" ), wxICON_EXCLAMATION | wxYES_NO, aParent )
421 == wxNO )
423
424 isUpdate = true;
425
426 compile_keep_on_update_regex( package, package.versions[0], keep_on_update );
427 }
428
429 m_reporter = std::make_unique<DIALOG_PCM_PROGRESS>( aParent, false );
430#ifdef __WXMAC__
431 m_reporter->ShowWindowModal();
432#else
433 m_reporter->Show();
434#endif
435
436 if( isUpdate )
437 {
438 m_reporter->PCMReport( wxString::Format( _( "Removing previous version of package '%s'." ),
439 package.name ),
441
442 deletePackageDirectories( package.identifier, keep_on_update );
443 }
444
445 if( extract( aFilePath, package.identifier, false ) )
446 m_pcm->MarkInstalled( package, package.versions[0].version, "" );
447
448 m_reporter->SetFinished();
449 m_pcm->ShowApiEnablePromptIfNeeded();
450 m_reporter->KeepRefreshing( false );
451 m_reporter->Destroy();
452 m_reporter.reset();
453
454 aParent->Raise();
455
456 std::unique_lock lock( m_changed_package_types_guard );
457 m_changed_package_types.insert( package.type );
459}
460
461
462class PATH_COLLECTOR : public wxDirTraverser
463{
464private:
465 std::vector<wxString>& m_files;
466 std::vector<wxString>& m_dirs;
467
468public:
469 explicit PATH_COLLECTOR( std::vector<wxString>& aFiles, std::vector<wxString>& aDirs ) :
470 m_files( aFiles ), m_dirs( aDirs )
471 {
472 }
473
474 wxDirTraverseResult OnFile( const wxString& aFilePath ) override
475 {
476 m_files.push_back( aFilePath );
477 return wxDIR_CONTINUE;
478 }
479
480 wxDirTraverseResult OnDir( const wxString& dirPath ) override
481 {
482 m_dirs.push_back( dirPath );
483 return wxDIR_CONTINUE;
484 }
485};
486
487
488void PCM_TASK_MANAGER::deletePackageDirectories( const wxString& aPackageId,
489 const std::forward_list<wxRegEx>& aKeep )
490{
491 // Namespace delimiter changed on disk to allow flat loading of Python modules
492 wxString clean_package_id = aPackageId;
493 clean_package_id.Replace( '.', '_' );
494
495 int path_prefix_len = m_pcm->Get3rdPartyPath().Length();
496
497 auto sort_func = []( const wxString& a, const wxString& b )
498 {
499 if( a.length() > b.length() )
500 return true;
501 if( a.length() < b.length() )
502 return false;
503
504 if( a != b )
505 return a < b;
506
507 return false;
508 };
509
510 for( const wxString& dir : PCM_PACKAGE_DIRECTORIES )
511 {
512 wxFileName d( m_pcm->Get3rdPartyPath(), "" );
513 d.AppendDir( dir );
514 d.AppendDir( clean_package_id );
515
516 if( !d.DirExists() )
517 continue;
518
519 m_reporter->PCMReport( wxString::Format( _( "Removing directory %s" ), d.GetPath() ),
521
522 if( aKeep.empty() )
523 {
524 if( !d.Rmdir( wxPATH_RMDIR_RECURSIVE ) )
525 {
526 m_reporter->PCMReport(
527 wxString::Format( _( "Failed to remove directory %s" ), d.GetPath() ),
529 }
530 }
531 else
532 {
533 std::vector<wxString> files;
534 std::vector<wxString> dirs;
535 PATH_COLLECTOR collector( files, dirs );
536
537 wxDir( d.GetFullPath() )
538 .Traverse( collector, wxEmptyString, wxDIR_DEFAULT | wxDIR_NO_FOLLOW );
539
540 // Do a poor mans post order traversal by sorting paths in reverse length order
541 std::sort( files.begin(), files.end(), sort_func );
542 std::sort( dirs.begin(), dirs.end(), sort_func );
543
544 // Delete files that don't match any of the aKeep regexs
545 for( const wxString& file : files )
546 {
547 bool del = true;
548
549 for( const wxRegEx& re : aKeep )
550 {
551 wxString tmp = file.Mid( path_prefix_len );
552 tmp.Replace( "\\", "/" );
553
554 if( re.Matches( tmp ) )
555 {
556 del = false;
557 break;
558 }
559 }
560
561 if( del )
562 wxRemoveFile( file );
563 }
564
565 // Delete any empty dirs
566 for( const wxString& empty_dir : dirs )
567 {
568 wxFileName dname( empty_dir, "" );
569 dname.Rmdir(); // not passing any flags here will only remove empty directories
570 }
571 }
572 }
573}
574
575
577{
578 PCM_TASK task = [aPackage, this]
579 {
581
582 m_pcm->MarkUninstalled( aPackage );
583
584 std::unique_lock lock( m_changed_package_types_guard );
585 m_changed_package_types.insert( aPackage.type );
586
587 m_reporter->PCMReport(
588 wxString::Format( _( "Package %s uninstalled" ), aPackage.name ),
591 };
592
593 m_install_queue.push( task );
595}
596
597
598void PCM_TASK_MANAGER::RunQueue( wxWindow* aParent )
599{
600 m_reporter = std::make_unique<DIALOG_PCM_PROGRESS>( aParent );
601
602 m_reporter->SetNumPhases( m_download_queue.size() + m_install_queue.size() );
603#ifdef __WXMAC__
604 m_reporter->ShowWindowModal();
605#else
606 m_reporter->Show();
607#endif
608
609 wxSafeYield();
610
611 std::mutex mutex;
612 std::condition_variable condvar;
613 bool download_complete = false;
614 int count_tasks = 0;
615 int count_failed_tasks = 0;
616 int count_success_tasks = 0;
617
618 std::thread download_thread(
619 [&]()
620 {
621 while( !m_download_queue.empty() && !m_reporter->IsCancelled() )
622 {
623 PCM_TASK task;
624 m_download_queue.pop( task );
625 PCM_TASK_MANAGER::STATUS task_status = task();
626
627 count_tasks++;
628
629 if( task_status == PCM_TASK_MANAGER::STATUS::SUCCESS )
630 count_success_tasks++;
631 else if( task_status != PCM_TASK_MANAGER::STATUS::INITIALIZED )
632 count_failed_tasks++;
633
634 m_reporter->AdvancePhase();
635
636 condvar.notify_all();
637 }
638
639 std::unique_lock<std::mutex> lock( mutex );
640 download_complete = true;
641 condvar.notify_all();
642 } );
643
644 std::thread install_thread(
645 [&]()
646 {
647 std::unique_lock<std::mutex> lock( mutex );
648
649 do
650 {
651 condvar.wait( lock,
652 [&]()
653 {
654 return download_complete || !m_install_queue.empty()
655 || m_reporter->IsCancelled();
656 } );
657
658 lock.unlock();
659
660 while( !m_install_queue.empty() && !m_reporter->IsCancelled() )
661 {
662 PCM_TASK task;
663 m_install_queue.pop( task );
664 PCM_TASK_MANAGER::STATUS task_status = task();
665
666 count_tasks++;
667
668 if( task_status == PCM_TASK_MANAGER::STATUS::SUCCESS )
669 count_success_tasks++;
670 else if( task_status != PCM_TASK_MANAGER::STATUS::INITIALIZED )
671 count_failed_tasks++;
672
673 m_reporter->AdvancePhase();
674 }
675
676 lock.lock();
677
678 } while( ( !m_install_queue.empty() || !download_complete )
679 && !m_reporter->IsCancelled() );
680
681 if( count_failed_tasks != 0 )
682 {
683 m_reporter->PCMReport(
684 wxString::Format( _( "%d out of %d operations failed." ), count_failed_tasks, count_tasks ),
686 }
687 else
688 {
689 if( count_success_tasks == count_tasks )
690 {
691 m_reporter->PCMReport( _( "All operations completed successfully." ), RPT_SEVERITY_INFO );
692 }
693 else
694 {
695 m_reporter->PCMReport(
696 wxString::Format( _( "%d out of %d operations were initialized but not successful." ),
697 count_tasks - count_success_tasks, count_tasks ),
699 }
700 }
701
702 m_reporter->SetFinished();
703 } );
704
705 m_reporter->KeepRefreshing( true );
706
707 download_thread.join();
708 install_thread.join();
709
710 // Show deferred API enable prompt when threads are done
711 m_pcm->ShowApiEnablePromptIfNeeded();
712
713 // Destroy the reporter only after the threads joined
714 // Incase the reporter terminated due to cancellation
715 m_reporter->Destroy();
716 m_reporter.reset();
717
718 aParent->Raise();
719}
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.
Definition paths.cpp:460
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.
Definition pcm.cpp:651
#define _(s)
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)
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO
< Package version metadataPackage metadata
Definition pcm_data.h:92
std::vector< std::string > keep_on_update
Definition pcm_data.h:103
Definition pcm_data.h:159
Repository reference to a resource.
Definition pcm_data.h:114
wxString identifier
Definition pcm_data.h:118
wxString name
Definition pcm_data.h:115
std::vector< PACKAGE_VERSION > versions
Definition pcm_data.h:127
PCM_PACKAGE_TYPE type
Definition pcm_data.h:119
std::vector< std::string > keep_on_update
Definition pcm_data.h:126
static bool CopyStreamData(wxInputStream &inputStream, wxOutputStream &outputStream, wxFileOffset size)