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