KiCad PCB EDA Suite
pcm.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 (C) 1992-2021 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 
21 // kicad_curl.h *must be* included before any wxWidgets header to avoid conflicts
22 // at least on Windows/msys2
23 #include <kicad_curl/kicad_curl.h>
25 
26 #include "pcm.h"
27 #include "core/wx_stl_compat.h"
28 #include "kicad_build_version.h"
29 #include "paths.h"
30 #include "pgm_base.h"
31 #include "picosha2.h"
34 
35 #include <fstream>
36 #include <iomanip>
37 #include <memory>
38 #include <wx/dir.h>
39 #include <wx/filefn.h>
40 #include <wx/fs_zip.h>
41 #include <wx/image.h>
42 #include <wx/mstream.h>
43 #include <wx/sstream.h>
44 #include <wx/tokenzr.h>
45 #include <wx/wfstream.h>
46 #include <wx/zipstrm.h>
47 
48 
49 const std::tuple<int, int> PLUGIN_CONTENT_MANAGER::m_kicad_version =
50  KICAD_MAJOR_MINOR_VERSION_TUPLE;
51 
52 
53 PLUGIN_CONTENT_MANAGER::PLUGIN_CONTENT_MANAGER( wxWindow* aParent ) : m_dialog( aParent )
54 {
55  // Get 3rd party path
56  const ENV_VAR_MAP& env = Pgm().GetLocalEnvVariables();
57  auto it = env.find( "KICAD6_3RD_PARTY" );
58 
59  if( it != env.end() && !it->second.GetValue().IsEmpty() )
60  m_3rdparty_path = it->second.GetValue();
61  else
63 
64  // Read and store pcm schema
65  wxFileName schema_file( PATHS::GetStockDataPath(), "pcm.v1.schema.json" );
66  schema_file.AppendDir( "schemas" );
67 
68  std::ifstream schema_stream( schema_file.GetFullPath().ToUTF8() );
69  nlohmann::json schema;
70 
71  try
72  {
73  schema_stream >> schema;
74  m_schema_validator.set_root_schema( schema );
75  }
76  catch( std::exception& e )
77  {
78  if( !schema_file.FileExists() )
79  wxLogError( wxString::Format( _( "schema file '%s' not found" ),
80  schema_file.GetFullPath() ) );
81  else
82  wxLogError( wxString::Format( _( "Error loading schema: %s" ), e.what() ) );
83  }
84 
85  // Load currently installed packages
86  wxFileName f( SETTINGS_MANAGER::GetUserSettingsPath(), "installed_packages.json" );
87 
88  if( f.FileExists() )
89  {
90  std::ifstream installed_stream( f.GetFullPath().ToUTF8() );
91  nlohmann::json installed;
92 
93  try
94  {
95  installed_stream >> installed;
96 
97  if( installed.contains( "packages" ) && installed["packages"].is_array() )
98  {
99  for( const auto& js_entry : installed["packages"] )
100  {
101  PCM_INSTALLATION_ENTRY entry = js_entry.get<PCM_INSTALLATION_ENTRY>();
102  m_installed.emplace( entry.package.identifier, entry );
103  }
104  }
105  }
106  catch( std::exception& e )
107  {
108  wxLogError( wxString::Format( _( "Error loading installed packages list: %s" ),
109  e.what() ) );
110  }
111  }
112 
113  // As a fall back populate installed from names of directories
114 
115  for( const wxString& dir : PCM_PACKAGE_DIRECTORIES )
116  {
117  wxFileName d( m_3rdparty_path, "" );
118  d.AppendDir( dir );
119 
120  if( d.DirExists() )
121  {
122  wxDir package_dir( d.GetPath() );
123 
124  if( !package_dir.IsOpened() )
125  continue;
126 
127  wxString subdir;
128  bool more = package_dir.GetFirst( &subdir, "", wxDIR_DIRS | wxDIR_HIDDEN );
129 
130  while( more )
131  {
132  if( m_installed.find( subdir ) == m_installed.end() )
133  {
135  wxFileName subdir_file( d.GetPath(), subdir );
136 
137  // wxFileModificationTime bugs out on windows for directories
138  wxStructStat stat;
139  wxStat( subdir_file.GetFullPath(), &stat );
140 
141  entry.package.name = subdir;
142  entry.package.identifier = subdir;
143  entry.current_version = "0.0";
144  entry.install_timestamp = stat.st_mtime;
145  entry.repository_name = wxT( "<unknown>" );
146 
147  m_installed.emplace( subdir, entry );
148  }
149 
150  more = package_dir.GetNext( &subdir );
151  }
152  }
153  }
154 
155  // Calculate package compatibility
156  std::for_each( m_installed.begin(), m_installed.end(),
157  [&]( auto& entry )
158  {
159  preparePackage( entry.second.package );
160  } );
161 }
162 
163 
164 bool PLUGIN_CONTENT_MANAGER::DownloadToStream( const wxString& aUrl, std::ostream* aOutput,
165  const wxString& aDialogTitle,
166  const size_t aSizeLimit )
167 {
168  bool size_exceeded = false;
169 
170  std::unique_ptr<WX_PROGRESS_REPORTER> reporter(
171  new WX_PROGRESS_REPORTER( m_dialog, aDialogTitle, 1 ) );
172 
173  TRANSFER_CALLBACK callback = [&]( size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow )
174  {
175  if( aSizeLimit > 0 && ( dltotal > aSizeLimit || dlnow > aSizeLimit ) )
176  {
177  size_exceeded = true;
178 
179  // Non zero return means abort.
180  return true;
181  }
182 
183  if( dltotal > 1024 )
184  {
185  reporter->SetCurrentProgress( dlnow / (double) dltotal );
186  reporter->Report( wxString::Format( _( "Downloading %lld/%lld Kb" ), dlnow / 1024,
187  dltotal / 1024 ) );
188  }
189  else
190  {
191  reporter->SetCurrentProgress( 0.0 );
192  }
193 
194  return !reporter->KeepRefreshing();
195  };
196 
197  KICAD_CURL_EASY curl;
198  curl.SetOutputStream( aOutput );
199  curl.SetURL( aUrl.ToUTF8().data() );
200  curl.SetFollowRedirects( true );
201  curl.SetTransferCallback( callback, 250000L );
202 
203  int code = curl.Perform();
204 
205  if( !reporter->IsCancelled() )
206  reporter->SetCurrentProgress( 1.0 );
207 
208  if( code != CURLE_OK )
209  {
210  if( code == CURLE_ABORTED_BY_CALLBACK && size_exceeded )
211  wxMessageBox( _( "Download is too large." ) );
212  else if( code != CURLE_ABORTED_BY_CALLBACK )
213  wxLogError( wxString( curl.GetErrorText( code ) ) );
214 
215  return false;
216  }
217 
218  return true;
219 }
220 
221 
222 bool PLUGIN_CONTENT_MANAGER::FetchRepository( const wxString& aUrl, PCM_REPOSITORY& aRepository )
223 {
224  std::stringstream repository_stream;
225  if( !DownloadToStream( aUrl, &repository_stream, _( "Fetching repository" ), 20480 ) )
226  {
227  wxLogError( _( "Unable to load repository url" ) );
228  return false;
229  }
230 
231  nlohmann::json repository_json;
232 
233  try
234  {
235  repository_stream >> repository_json;
236 
237  ValidateJson( repository_json, nlohmann::json_uri( "#/definitions/Repository" ) );
238 
239  aRepository = repository_json.get<PCM_REPOSITORY>();
240  }
241  catch( const std::exception& e )
242  {
243  wxLogError( wxString::Format( _( "Unable to parse repository:\n\n%s" ), e.what() ) );
244  return false;
245  }
246 
247  return true;
248 }
249 
250 
252  const nlohmann::json_uri& aUri ) const
253 {
254  nlohmann::json_schema::basic_error_handler error_handler;
255  m_schema_validator.validate( aJson, error_handler, aUri );
256 }
257 
258 
259 bool PLUGIN_CONTENT_MANAGER::fetchPackages( const wxString& aUrl,
260  const boost::optional<wxString>& aHash,
261  std::vector<PCM_PACKAGE>& aPackages )
262 {
263  std::stringstream packages_stream;
264 
265  if( !DownloadToStream( aUrl, &packages_stream, _( "Fetching repository packages" ) ) )
266  {
267  wxLogError( _( "Unable to load repository packages url." ) );
268  return false;
269  }
270 
271  std::istringstream isstream( packages_stream.str() );
272 
273  if( aHash && !VerifyHash( isstream, aHash.get() ) )
274  {
275  wxLogError( _( "Packages hash doesn't match. Repository may be corrupted." ) );
276  return false;
277  }
278 
279  try
280  {
281  nlohmann::json packages_json = nlohmann::json::parse( packages_stream.str() );
282  ValidateJson( packages_json, nlohmann::json_uri( "#/definitions/PackageArray" ) );
283 
284  for( nlohmann::json& package : packages_json["packages"] )
285  {
286  aPackages.push_back( package.get<PCM_PACKAGE>() );
287  }
288  }
289  catch( std::exception& e )
290  {
291  wxLogError( wxString::Format( _( "Unable to parse packages metadata:\n\n%s" ), e.what() ) );
292  return false;
293  }
294 
295  return true;
296 }
297 
298 
299 bool PLUGIN_CONTENT_MANAGER::VerifyHash( std::istream& aStream, const wxString& aHash ) const
300 {
301  std::vector<unsigned char> bytes( picosha2::k_digest_size );
302 
303  picosha2::hash256( std::istreambuf_iterator<char>( aStream ), std::istreambuf_iterator<char>(),
304  bytes.begin(), bytes.end() );
305  std::string hex_str = picosha2::bytes_to_hex_string( bytes.begin(), bytes.end() );
306 
307  return aHash.compare( hex_str ) == 0;
308 }
309 
310 
311 const PCM_REPOSITORY&
312 PLUGIN_CONTENT_MANAGER::getCachedRepository( const wxString& aRepositoryId ) const
313 {
314  wxASSERT_MSG( m_repository_cache.find( aRepositoryId ) != m_repository_cache.end(),
315  "Repository is not cached." );
316 
317  return m_repository_cache.at( aRepositoryId );
318 }
319 
320 
321 const bool PLUGIN_CONTENT_MANAGER::CacheRepository( const wxString& aRepositoryId )
322 {
323  if( m_repository_cache.find( aRepositoryId ) != m_repository_cache.end() )
324  return true;
325 
326  const auto repository_tuple =
327  std::find_if( m_repository_list.begin(), m_repository_list.end(),
328  [&aRepositoryId]( const std::tuple<wxString, wxString, wxString>& t )
329  {
330  return std::get<0>( t ) == aRepositoryId;
331  } );
332 
333  if( repository_tuple == m_repository_list.end() )
334  return false;
335 
336  wxString url = std::get<2>( *repository_tuple );
337 
338  nlohmann::json js;
339  PCM_REPOSITORY current_repo;
340 
341  if( !FetchRepository( url, current_repo ) )
342  return false;
343 
344  bool packages_cache_exists = false;
345 
346  // First load repository data from local filesystem if available.
347  wxFileName repo_cache = wxFileName( m_3rdparty_path, "repository.json" );
348  repo_cache.AppendDir( "cache" );
349  repo_cache.AppendDir( aRepositoryId );
350  wxFileName packages_cache( repo_cache.GetPath(), "packages.json" );
351 
352  if( repo_cache.FileExists() && packages_cache.FileExists() )
353  {
354  std::ifstream repo_stream( repo_cache.GetFullPath().ToUTF8() );
355  repo_stream >> js;
356  PCM_REPOSITORY saved_repo = js.get<PCM_REPOSITORY>();
357 
358  if( saved_repo.packages.update_timestamp == current_repo.packages.update_timestamp )
359  {
360  // Cached repo is up to date, use data on disk
361  js.clear();
362  std::ifstream packages_cache_stream( packages_cache.GetFullPath().ToUTF8() );
363 
364  try
365  {
366  packages_cache_stream >> js;
367  saved_repo.package_list = js["packages"].get<std::vector<PCM_PACKAGE>>();
368 
369  std::for_each( saved_repo.package_list.begin(), saved_repo.package_list.end(),
370  &preparePackage );
371 
372  m_repository_cache[aRepositoryId] = std::move( saved_repo );
373 
374  packages_cache_exists = true;
375  }
376  catch( ... )
377  {
378  wxLogError( _( "Packages cache for current repository is "
379  "corrupted, it will be redownloaded." ) );
380  }
381  }
382  }
383 
384  if( !packages_cache_exists )
385  {
386  // Cache doesn't exist or is out of date
387  if( !fetchPackages( current_repo.packages.url, current_repo.packages.sha256,
388  current_repo.package_list ) )
389  {
390  return false;
391  }
392 
393  std::for_each( current_repo.package_list.begin(), current_repo.package_list.end(),
394  &preparePackage );
395 
396  repo_cache.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
397 
398  std::ofstream repo_cache_stream( repo_cache.GetFullPath().ToUTF8() );
399  repo_cache_stream << std::setw( 4 ) << nlohmann::json( current_repo ) << std::endl;
400 
401  std::ofstream packages_cache_stream( packages_cache.GetFullPath().ToUTF8() );
402  js.clear();
403  js["packages"] = nlohmann::json( current_repo.package_list );
404  packages_cache_stream << std::setw( 4 ) << js << std::endl;
405 
406  m_repository_cache[aRepositoryId] = std::move( current_repo );
407  }
408 
409  if( current_repo.resources )
410  {
411  // Check resources file date, redownload if needed
412  PCM_RESOURCE_REFERENCE& resources = current_repo.resources.get();
413 
414  wxFileName resource_file( repo_cache.GetPath(), "resources.zip" );
415 
416  time_t mtime = 0;
417 
418  if( resource_file.FileExists() )
419  mtime = wxFileModificationTime( resource_file.GetFullPath() );
420 
421  if( mtime + 600 < getCurrentTimestamp() && mtime < (time_t) resources.update_timestamp )
422  {
423  std::ofstream resources_stream( resource_file.GetFullPath().ToUTF8(),
424  std::ios_base::binary );
425 
426  // 100 Mb resource file limit
427  bool success = DownloadToStream( resources.url, &resources_stream,
428  _( "Downloading resources" ), 100 * 1024 * 1024 );
429 
430  resources_stream.close();
431 
432  if( success )
433  {
434  std::ifstream read_stream( resource_file.GetFullPath().ToUTF8(),
435  std::ios_base::binary );
436 
437 
438  if( resources.sha256 && !VerifyHash( read_stream, resources.sha256.get() ) )
439  {
440  read_stream.close();
441  wxLogError(
442  _( "Resources file hash doesn't match and will not be used. Repository "
443  "may be corrupted." ) );
444  wxRemoveFile( resource_file.GetFullPath() );
445  }
446  }
447  else
448  {
449  // Not critical, just clean up the file
450  wxRemoveFile( resource_file.GetFullPath() );
451  }
452  }
453  }
454 
455  return true;
456 }
457 
458 
460 {
461  // Parse package version strings
462  for( PACKAGE_VERSION& ver : aPackage.versions )
463  {
464  int epoch = 0, major = 0, minor = 0, patch = 0;
465 
466  if( ver.version_epoch )
467  epoch = ver.version_epoch.get();
468 
469  wxStringTokenizer version_tokenizer( ver.version, "." );
470 
471  major = wxAtoi( version_tokenizer.GetNextToken() );
472 
473  if( version_tokenizer.HasMoreTokens() )
474  minor = wxAtoi( version_tokenizer.GetNextToken() );
475 
476  if( version_tokenizer.HasMoreTokens() )
477  patch = wxAtoi( version_tokenizer.GetNextToken() );
478 
479  ver.parsed_version = std::make_tuple( epoch, major, minor, patch );
480 
481  // Determine compatibility
482  ver.compatible = true;
483 
484  auto parse_major_minor = []( const wxString& version )
485  {
486  wxStringTokenizer tokenizer( version, "." );
487  int ver_major = wxAtoi( tokenizer.GetNextToken() );
488  int ver_minor = wxAtoi( tokenizer.GetNextToken() );
489  return std::tuple<int, int>( ver_major, ver_minor );
490  };
491 
492  if( parse_major_minor( ver.kicad_version ) > m_kicad_version )
493  ver.compatible = false;
494 
495  if( ver.kicad_version_max
496  && parse_major_minor( ver.kicad_version_max.get() ) < m_kicad_version )
497  ver.compatible = false;
498 
499 #ifdef __WXMSW__
500  wxString platform = wxT( "windows" );
501 #endif
502 #ifdef __WXOSX__
503  wxString platform = wxT( "macos" );
504 #endif
505 #ifdef __WXGTK__
506  wxString platform = wxT( "linux" );
507 #endif
508 
509  if( ver.platforms.size() > 0
510  && std::find( ver.platforms.begin(), ver.platforms.end(), platform )
511  == ver.platforms.end() )
512  {
513  ver.compatible = false;
514  }
515  }
516 
517  // Sort by descending version
518  std::sort( aPackage.versions.begin(), aPackage.versions.end(),
519  []( const PACKAGE_VERSION& a, const PACKAGE_VERSION& b )
520  {
521  return a.parsed_version > b.parsed_version;
522  } );
523 }
524 
525 
526 const std::vector<PCM_PACKAGE>&
527 PLUGIN_CONTENT_MANAGER::GetRepositoryPackages( const wxString& aRepositoryId ) const
528 {
529  return getCachedRepository( aRepositoryId ).package_list;
530 }
531 
532 
534 {
535  // Clean up cache folder if repository is not in new list
536  for( const auto& entry : m_repository_list )
537  {
538  auto it = std::find_if( aRepositories.begin(), aRepositories.end(),
539  [&]( const auto& new_entry )
540  {
541  return new_entry.first == std::get<1>( entry );
542  } );
543 
544  if( it == aRepositories.end() )
545  {
546  DiscardRepositoryCache( std::get<0>( entry ) );
547  }
548  }
549 
550  m_repository_list.clear();
551  m_repository_cache.clear();
552 
553  for( const auto& repo : aRepositories )
554  {
555  std::string url_sha = picosha2::hash256_hex_string( repo.second );
556  m_repository_list.push_back(
557  std::make_tuple( url_sha.substr( 0, 16 ), repo.first, repo.second ) );
558  }
559 }
560 
561 
562 void PLUGIN_CONTENT_MANAGER::DiscardRepositoryCache( const wxString& aRepositoryId )
563 {
564  if( m_repository_cache.count( aRepositoryId ) > 0 )
565  m_repository_cache.erase( aRepositoryId );
566 
567  wxFileName repo_cache( m_3rdparty_path, "" );
568  repo_cache.AppendDir( "cache" );
569  repo_cache.AppendDir( aRepositoryId );
570 
571  if( repo_cache.DirExists() )
572  repo_cache.Rmdir( wxPATH_RMDIR_RECURSIVE );
573 }
574 
575 
576 void PLUGIN_CONTENT_MANAGER::MarkInstalled( const PCM_PACKAGE& aPackage, const wxString& aVersion,
577  const wxString& aRepositoryId )
578 {
580  entry.package = aPackage;
581  entry.current_version = aVersion;
582  entry.repository_id = aRepositoryId;
583 
584  if( !aRepositoryId.IsEmpty() )
585  entry.repository_name = getCachedRepository( aRepositoryId ).name;
586  else
587  entry.repository_name = _( "Local file" );
588 
590 
591  m_installed.emplace( aPackage.identifier, entry );
592 }
593 
594 
596 {
597  m_installed.erase( aPackage.identifier );
598 }
599 
600 
602  const wxString& aPackageId )
603 {
604  if( m_installed.find( aPackageId ) != m_installed.end() )
605  return PPS_INSTALLED;
606 
607  if( aRepositoryId.IsEmpty() || !CacheRepository( aRepositoryId ) )
608  return PPS_UNAVAILABLE;
609 
610  const PCM_REPOSITORY& repo = getCachedRepository( aRepositoryId );
611 
612  auto pkg_it = std::find_if( repo.package_list.begin(), repo.package_list.end(),
613  [&aPackageId]( const PCM_PACKAGE& pkg )
614  {
615  return pkg.identifier == aPackageId;
616  } );
617 
618  if( pkg_it == repo.package_list.end() )
619  return PPS_UNAVAILABLE;
620 
621  const PCM_PACKAGE& pkg = *pkg_it;
622 
623  auto ver_it = std::find_if( pkg.versions.begin(), pkg.versions.end(),
624  []( const PACKAGE_VERSION& ver )
625  {
626  return ver.compatible;
627  } );
628 
629  if( ver_it == pkg.versions.end() )
630  return PPS_UNAVAILABLE;
631  else
632  return PPS_AVAILABLE;
633 }
634 
635 
637 {
638  return std::chrono::duration_cast<std::chrono::seconds>(
639  std::chrono::system_clock::now().time_since_epoch() )
640  .count();
641 }
642 
643 
645 {
646  // Save current installed packages list.
647 
648  nlohmann::json js;
649  js["packages"] = nlohmann::json::array();
650 
651  for( const auto& entry : m_installed )
652  {
653  js["packages"].emplace_back( entry.second );
654  }
655 
656  wxFileName f( SETTINGS_MANAGER::GetUserSettingsPath(), "installed_packages.json" );
657  std::ofstream stream( f.GetFullPath().ToUTF8() );
658 
659  stream << std::setw( 4 ) << js << std::endl;
660 }
661 
662 
663 const std::vector<PCM_INSTALLATION_ENTRY> PLUGIN_CONTENT_MANAGER::GetInstalledPackages() const
664 {
665  std::vector<PCM_INSTALLATION_ENTRY> v;
666 
667  std::for_each( m_installed.begin(), m_installed.end(),
668  [&v]( const auto& entry )
669  {
670  v.push_back( entry.second );
671  } );
672 
673  return v;
674 }
675 
676 
677 const wxString&
678 PLUGIN_CONTENT_MANAGER::GetInstalledPackageVersion( const wxString& aPackageId ) const
679 {
680  wxASSERT_MSG( m_installed.find( aPackageId ) != m_installed.end(),
681  "Installed package not found." );
682 
683  return m_installed.at( aPackageId ).current_version;
684 }
685 
686 
688  const wxString& aSearchTerm )
689 {
690  wxArrayString terms = wxStringTokenize( aSearchTerm.Lower(), " ", wxTOKEN_STRTOK );
691  int rank = 0;
692 
693  const auto find_term_matches = [&]( const wxString& str )
694  {
695  int result = 0;
696  wxString lower = str.Lower();
697 
698  for( const wxString& term : terms )
699  if( lower.Find( term ) != wxNOT_FOUND )
700  result += 1;
701 
702  return result;
703  };
704 
705  // Match on package id
706  if( terms.size() == 1 && terms[0] == aPackage.identifier )
707  rank += 10000;
708 
709  if( terms.size() == 1 && find_term_matches( aPackage.identifier ) )
710  rank += 1000;
711 
712  // Match on package name
713  rank += 500 * find_term_matches( aPackage.name );
714 
715  // Match on tags
716  for( const wxString& tag : aPackage.tags )
717  rank += 100 * find_term_matches( tag );
718 
719  // Match on package description
720  rank += 10 * find_term_matches( aPackage.description );
721  rank += 10 * find_term_matches( aPackage.description_full );
722 
723  // Match on author/maintainer
724  rank += find_term_matches( aPackage.author.name );
725 
726  if( aPackage.maintainer )
727  rank += 3 * find_term_matches( aPackage.maintainer.get().name );
728 
729  // Match on resources
730  for( const auto& entry : aPackage.resources )
731  {
732  rank += find_term_matches( entry.first );
733  rank += find_term_matches( entry.second );
734  }
735 
736  // Match on license
737  if( terms.size() == 1 && terms[0] == aPackage.license )
738  rank += 1;
739 
740  return rank;
741 }
742 
743 
744 std::unordered_map<wxString, wxBitmap>
746 {
747  std::unordered_map<wxString, wxBitmap> bitmaps;
748 
749  wxFileName resources_file = wxFileName( m_3rdparty_path, "resources.zip" );
750  resources_file.AppendDir( "cache" );
751  resources_file.AppendDir( aRepositoryId );
752 
753  if( !resources_file.FileExists() )
754  return bitmaps;
755 
756  wxFFileInputStream stream( resources_file.GetFullPath() );
757  wxZipInputStream zip( stream );
758 
759  if( !zip.IsOk() || zip.GetTotalEntries() == 0 )
760  return bitmaps;
761 
762  for( wxArchiveEntry* entry = zip.GetNextEntry(); entry; entry = zip.GetNextEntry() )
763  {
764  wxArrayString path_parts =
765  wxSplit( entry->GetName(), wxFileName::GetPathSeparator(), (wxChar) NULL );
766 
767  if( path_parts.size() != 2 || path_parts[1] != "icon.png" )
768  continue;
769 
770  try
771  {
772  wxMemoryInputStream image_stream( zip, entry->GetSize() );
773  wxImage image( image_stream, wxBITMAP_TYPE_PNG );
774  bitmaps.emplace( path_parts[0], wxBitmap( image ) );
775  }
776  catch( ... )
777  {
778  // Log and ignore
779  wxLogTrace( "Error loading png bitmap for entry %s from %s", entry->GetName(),
780  resources_file.GetFullPath() );
781  }
782  }
783 
784  return bitmaps;
785 }
786 
787 
788 std::unordered_map<wxString, wxBitmap> PLUGIN_CONTENT_MANAGER::GetInstalledPackageBitmaps()
789 {
790  std::unordered_map<wxString, wxBitmap> bitmaps;
791 
792  wxFileName resources_dir_fn( m_3rdparty_path, "" );
793  resources_dir_fn.AppendDir( "resources" );
794  wxDir resources_dir( resources_dir_fn.GetPath() );
795 
796  if( !resources_dir.IsOpened() )
797  return bitmaps;
798 
799  wxString subdir;
800  bool more = resources_dir.GetFirst( &subdir, "", wxDIR_DIRS | wxDIR_HIDDEN );
801 
802  while( more )
803  {
804  wxFileName icon( resources_dir_fn.GetPath(), "icon.png" );
805  icon.AppendDir( subdir );
806 
807  if( icon.FileExists() )
808  {
809  try
810  {
811  wxBitmap bitmap( icon.GetFullPath(), wxBITMAP_TYPE_PNG );
812  bitmaps.emplace( subdir, bitmap );
813  }
814  catch( ... )
815  {
816  // Log and ignore
817  wxLogTrace( "Error loading png bitmap from %s", icon.GetFullPath() );
818  }
819  }
820 
821  more = resources_dir.GetNext( &subdir );
822  }
823 
824  return bitmaps;
825 }
< Package version metadata Package metadata
Definition: pcm_data.h:73
std::unordered_map< wxString, wxBitmap > GetRepositoryPackageBitmaps(const wxString &aRepositoryId)
Get the icon bitmaps for repository packages.
Definition: pcm.cpp:745
const wxString & GetInstalledPackageVersion(const wxString &aPackageId) const
Get the current version of an installed package.
Definition: pcm.cpp:678
bool SetFollowRedirects(bool aFollow)
Enable the following of HTTP(s) and other redirects, by default curl does not follow redirects.
boost::optional< PCM_CONTACT > maintainer
Definition: pcm_data.h:101
std::vector< std::string > platforms
Definition: pcm_data.h:82
PCM_RESOURCE_REFERENCE packages
Definition: pcm_data.h:122
PLUGIN_CONTENT_MANAGER(wxWindow *aParent)
Definition: pcm.cpp:53
uint64_t update_timestamp
Definition: pcm_data.h:114
boost::optional< PCM_RESOURCE_REFERENCE > resources
Definition: pcm_data.h:123
std::map< wxString, PCM_INSTALLATION_ENTRY > m_installed
Definition: pcm.h:311
int GetPackageSearchRank(const PCM_PACKAGE &aPackage, const wxString &aSearchTerm)
Get the approximate measure of how much given package matches the search term.
Definition: pcm.cpp:687
bool parse(std::istream &aStream, bool aVerbose)
Parse a PCB or footprint file from the given input stream.
void DiscardRepositoryCache(const wxString &aRepositoryId)
Discard in-memory and on-disk cache of a repository.
Definition: pcm.cpp:562
void MarkUninstalled(const PCM_PACKAGE &aPackage)
Mark package as uninstalled.
Definition: pcm.cpp:595
wxString description
Definition: pcm_data.h:96
bool fetchPackages(const wxString &aUrl, const boost::optional< wxString > &aHash, std::vector< PCM_PACKAGE > &aPackages)
Downloads packages metadata to in memory stream, verifies hash and attempts to parse it.
Definition: pcm.cpp:259
wxString name
Definition: pcm_data.h:63
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:106
nlohmann::json json
Definition: gerbview.cpp:41
STRING_TUPLE_LIST m_repository_list
Definition: pcm.h:309
static wxString GetStockDataPath(bool aRespectRunFromBuildDir=true)
Gets the stock (install) data path, which is the base path for things like scripting,...
Definition: paths.cpp:150
static const std::tuple< int, int > m_kicad_version
Definition: pcm.h:312
boost::optional< int > version_epoch
Definition: pcm_data.h:76
bool compatible
Definition: pcm_data.h:88
wxString name
Definition: pcm_data.h:121
const std::unordered_set< wxString > PCM_PACKAGE_DIRECTORIES({ "plugins", "footprints", "models", "symbols", "resources", "colors", })
< Contains list of all valid directories that get extracted from a package archive
Multi-thread safe progress reporter dialog, intended for use of tasks that parallel reporting back of...
wxString description_full
Definition: pcm_data.h:97
void MarkInstalled(const PCM_PACKAGE &aPackage, const wxString &aVersion, const wxString &aRepositoryId)
Mark package as installed.
Definition: pcm.cpp:576
bool VerifyHash(std::istream &aStream, const wxString &aHash) const
Verifies SHA256 hash of a binary stream.
Definition: pcm.cpp:299
Repository reference to a resource.
Definition: pcm_data.h:93
const std::vector< PCM_PACKAGE > & GetRepositoryPackages(const wxString &aRepositoryId) const
Get the packages metadata from a previously cached repository.
Definition: pcm.cpp:527
std::function< int(size_t, size_t, size_t, size_t)> TRANSFER_CALLBACK
Wrapper interface around the curl_easy API/.
const PCM_REPOSITORY & getCachedRepository(const wxString &aRepositoryId) const
Get the cached repository metadata.
Definition: pcm.cpp:312
const bool CacheRepository(const wxString &aRepositoryId)
Cache specified repository packages and other metadata.
Definition: pcm.cpp:321
std::unordered_map< wxString, wxBitmap > GetInstalledPackageBitmaps()
Get the icon bitmaps for installed packages.
Definition: pcm.cpp:788
#define _(s)
std::vector< std::string > tags
Definition: pcm_data.h:104
wxString name
Definition: pcm_data.h:95
STRING_MAP resources
Definition: pcm_data.h:103
wxString m_3rdparty_path
Definition: pcm.h:306
wxWindow * m_dialog
Definition: pcm.h:304
int Perform()
Equivalent to curl_easy_perform.
std::vector< PACKAGE_VERSION > versions
Definition: pcm_data.h:105
const std::string GetErrorText(int aCode)
Fetch CURL's "friendly" error string for a given error code.
uint64_t install_timestamp
Definition: pcm_data.h:139
static wxString GetUserSettingsPath()
Return the user configuration path used to store KiCad's configuration files.
static void preparePackage(PCM_PACKAGE &aPackage)
Parses version strings and calculates compatibility.
Definition: pcm.cpp:459
bool SetTransferCallback(const TRANSFER_CALLBACK &aCallback, size_t aInterval)
bool SetURL(const std::string &aURL)
Set the request URL.
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
std::vector< std::pair< wxString, wxString > > STRING_PAIR_LIST
Definition: pcm.h:69
const std::vector< PCM_INSTALLATION_ENTRY > GetInstalledPackages() const
Get list of installed packages.
Definition: pcm.cpp:663
PCM_PACKAGE_STATE
Definition: pcm.h:51
wxString current_version
Definition: pcm_data.h:136
see class PGM_BASE
std::map< wxString, ENV_VAR_ITEM > ENV_VAR_MAP
std::tuple< int, int, int, int > parsed_version
Definition: pcm_data.h:87
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.
Definition: pcm.cpp:251
wxString identifier
Definition: pcm_data.h:98
wxString kicad_version
Definition: pcm_data.h:83
boost::optional< wxString > kicad_version_max
Definition: pcm_data.h:84
PCM_CONTACT author
Definition: pcm_data.h:100
wxString license
Definition: pcm_data.h:102
boost::optional< wxString > sha256
Definition: pcm_data.h:113
bool DownloadToStream(const wxString &aUrl, std::ostream *aOutput, const wxString &aDialogTitle, const size_t aSizeLimit=DEFAULT_DOWNLOAD_MEM_LIMIT)
Downloads url to an output stream.
Definition: pcm.cpp:164
std::vector< PCM_PACKAGE > package_list
Definition: pcm_data.h:128
PCM_PACKAGE_STATE GetPackageState(const wxString &aRepositoryId, const wxString &aPackageId)
Get current state of the package.
Definition: pcm.cpp:601
bool SetOutputStream(const std::ostream *aOutput)
nlohmann::json_schema::json_validator m_schema_validator
Definition: pcm.h:305
wxString repository_id
Definition: pcm_data.h:137
void SetRepositoryList(const STRING_PAIR_LIST &aRepositories)
Set list of repositories.
Definition: pcm.cpp:533
static wxString GetDefault3rdPartyPath()
Gets the default path for PCM packages.
Definition: paths.cpp:129
PCM_PACKAGE package
Definition: pcm_data.h:135
bool FetchRepository(const wxString &aUrl, PCM_REPOSITORY &aRepository)
Fetches repository metadata from given url.
Definition: pcm.cpp:222
wxString repository_name
Definition: pcm_data.h:138
Definition: pcm_data.h:133
Package installation entry.
Definition: pcm_data.h:119
Repository metadata.
Definition: pcm_data.h:110
wxString version
Definition: pcm_data.h:75
time_t getCurrentTimestamp() const
Definition: pcm.cpp:636
std::unordered_map< wxString, PCM_REPOSITORY > m_repository_cache
Definition: pcm.h:308