KiCad PCB EDA Suite
footprint_info_impl.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) 2011 Jean-Pierre Charras, <jp.charras@wanadoo.fr>
5  * Copyright (C) 2013-2016 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
6  * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
7  *
8  * This program is free software: you can redistribute it and/or modify it
9  * under the terms of the GNU General Public License as published by the
10  * Free Software Foundation, either version 3 of the License, or (at your
11  * option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16  * General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with this program. If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 
23 #include <footprint_info_impl.h>
24 
25 #include <footprint.h>
26 #include <footprint_info.h>
27 #include <fp_lib_table.h>
29 #include <string_utils.h>
30 #include <locale_io.h>
31 #include <kiway.h>
32 #include <lib_id.h>
34 #include <progress_reporter.h>
35 #include <wx/textfile.h>
36 #include <wx/txtstrm.h>
37 #include <wx/wfstream.h>
38 
39 #include <thread>
40 
41 
43 {
44  FP_LIB_TABLE* fptable = m_owner->GetTable();
45 
46  wxASSERT( fptable );
47 
48  const FOOTPRINT* footprint = fptable->GetEnumeratedFootprint( m_nickname, m_fpname );
49 
50  if( footprint == nullptr ) // Should happen only with malformed/broken libraries
51  {
52  m_pad_count = 0;
54  }
55  else
56  {
59  m_keywords = footprint->GetKeywords();
60  m_doc = footprint->GetDescription();
61  }
62 
63  m_loaded = true;
64 }
65 
66 
67 bool FOOTPRINT_LIST_IMPL::CatchErrors( const std::function<void()>& aFunc )
68 {
69  try
70  {
71  aFunc();
72  }
73  catch( const IO_ERROR& ioe )
74  {
75  m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
76  return false;
77  }
78  catch( const std::exception& se )
79  {
80  // This is a round about way to do this, but who knows what THROW_IO_ERROR()
81  // may be tricked out to do someday, keep it in the game.
82  try
83  {
84  THROW_IO_ERROR( se.what() );
85  }
86  catch( const IO_ERROR& ioe )
87  {
88  m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
89  }
90 
91  return false;
92  }
93 
94  return true;
95 }
96 
97 
99 {
100  wxString nickname;
101 
102  while( m_queue_in.pop( nickname ) && !m_cancelled )
103  {
104  CatchErrors( [this, &nickname]()
105  {
106  m_lib_table->PrefetchLib( nickname );
107  m_queue_out.push( nickname );
108  } );
109 
110  m_count_finished.fetch_add( 1 );
111 
112  if( m_progress_reporter )
114  }
115 }
116 
117 
118 bool FOOTPRINT_LIST_IMPL::ReadFootprintFiles( FP_LIB_TABLE* aTable, const wxString* aNickname,
119  PROGRESS_REPORTER* aProgressReporter )
120 {
121  long long int generatedTimestamp = aTable->GenerateTimestamp( aNickname );
122 
123  if( generatedTimestamp == m_list_timestamp )
124  return true;
125 
126  m_progress_reporter = aProgressReporter;
127 
128  if( m_progress_reporter )
129  {
131  m_progress_reporter->Report( _( "Fetching footprint libraries..." ) );
132  }
133 
134  m_cancelled = false;
135 
136  FOOTPRINT_ASYNC_LOADER loader;
137 
138  loader.SetList( this );
139  loader.Start( aTable, aNickname );
140 
141  while( !m_cancelled && (int)m_count_finished.load() < m_loader->m_total_libs )
142  {
144  m_cancelled = true;
145 
146  wxMilliSleep( 20 );
147  }
148 
149  if( m_cancelled )
150  {
151  loader.Abort();
152  }
153  else
154  {
155  if( m_progress_reporter )
156  {
159  m_progress_reporter->Report( _( "Loading footprints..." ) );
160  }
161 
162  loader.Join();
163 
164  if( m_progress_reporter )
166  }
167 
168  if( m_cancelled )
169  m_list_timestamp = 0; // God knows what we got before we were canceled
170  else
171  m_list_timestamp = generatedTimestamp;
172 
173  return m_errors.empty();
174 }
175 
176 
177 void FOOTPRINT_LIST_IMPL::startWorkers( FP_LIB_TABLE* aTable, wxString const* aNickname,
178  FOOTPRINT_ASYNC_LOADER* aLoader, unsigned aNThreads )
179 {
180  m_loader = aLoader;
181  m_lib_table = aTable;
182 
183  // Clear data before reading files
184  m_count_finished.store( 0 );
185  m_errors.clear();
186  m_list.clear();
187  m_threads.clear();
188  m_queue_in.clear();
189  m_queue_out.clear();
190 
191  if( aNickname )
192  {
193  m_queue_in.push( *aNickname );
194  }
195  else
196  {
197  for( auto const& nickname : aTable->GetLogicalLibs() )
198  m_queue_in.push( nickname );
199  }
200 
202 
203  for( unsigned i = 0; i < aNThreads; ++i )
204  {
205  m_threads.emplace_back( &FOOTPRINT_LIST_IMPL::loader_job, this );
206  }
207 }
208 
209 
211 {
212  std::lock_guard<std::mutex> lock1( m_join );
213 
214  // To safely stop our workers, we set the cancellation flag (they will each
215  // exit on their next safe loop location when this is set). Then we need to wait
216  // for all threads to finish as closing the implementation will free the queues
217  // that the threads write to.
218  for( auto& i : m_threads )
219  i.join();
220 
221  m_threads.clear();
222  m_queue_in.clear();
223  m_count_finished.store( 0 );
224 
225  // If we have canceled in the middle of a load, clear our timestamp to re-load next time
226  if( m_cancelled )
227  m_list_timestamp = 0;
228 }
229 
230 
232 {
233  {
234  std::lock_guard<std::mutex> lock1( m_join );
235 
236  for( auto& i : m_threads )
237  i.join();
238 
239  m_threads.clear();
240  m_queue_in.clear();
241  m_count_finished.store( 0 );
242  }
243 
244  size_t total_count = m_queue_out.size();
245 
246  LOCALE_IO toggle_locale;
247 
248  // Parse the footprints in parallel. WARNING! This requires changing the locale, which is
249  // GLOBAL. It is only thread safe to construct the LOCALE_IO before the threads are created,
250  // destroy it after they finish, and block the main (GUI) thread while they work. Any deviation
251  // from this will cause nasal demons.
252  //
253  // TODO: blast LOCALE_IO into the sun
254 
256  std::vector<std::thread> threads;
257 
258  for( size_t ii = 0; ii < std::thread::hardware_concurrency() + 1; ++ii )
259  {
260  threads.emplace_back( [this, &queue_parsed]() {
261  wxString nickname;
262 
263  while( m_queue_out.pop( nickname ) && !m_cancelled )
264  {
265  wxArrayString fpnames;
266 
267  try
268  {
269  m_lib_table->FootprintEnumerate( fpnames, nickname, false );
270  }
271  catch( const IO_ERROR& ioe )
272  {
273  m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
274  }
275  catch( const std::exception& se )
276  {
277  // This is a round about way to do this, but who knows what THROW_IO_ERROR()
278  // may be tricked out to do someday, keep it in the game.
279  try
280  {
281  THROW_IO_ERROR( se.what() );
282  }
283  catch( const IO_ERROR& ioe )
284  {
285  m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
286  }
287  }
288 
289  for( unsigned jj = 0; jj < fpnames.size() && !m_cancelled; ++jj )
290  {
291  wxString fpname = fpnames[jj];
292  FOOTPRINT_INFO* fpinfo = new FOOTPRINT_INFO_IMPL( this, nickname, fpname );
293  queue_parsed.move_push( std::unique_ptr<FOOTPRINT_INFO>( fpinfo ) );
294  }
295 
296  if( m_progress_reporter )
298 
299  m_count_finished.fetch_add( 1 );
300  }
301  } );
302  }
303 
304  while( !m_cancelled && (size_t)m_count_finished.load() < total_count )
305  {
307  m_cancelled = true;
308 
309  wxMilliSleep( 30 );
310  }
311 
312  for( auto& thr : threads )
313  thr.join();
314 
315  std::unique_ptr<FOOTPRINT_INFO> fpi;
316 
317  while( queue_parsed.pop( fpi ) )
318  m_list.push_back( std::move( fpi ) );
319 
320  std::sort( m_list.begin(), m_list.end(),
321  []( std::unique_ptr<FOOTPRINT_INFO> const& lhs,
322  std::unique_ptr<FOOTPRINT_INFO> const& rhs ) -> bool
323  {
324  return *lhs < *rhs;
325  } );
326 
327  return m_errors.empty();
328 }
329 
330 
332  m_loader( nullptr ),
333  m_count_finished( 0 ),
334  m_list_timestamp( 0 ),
335  m_progress_reporter( nullptr ),
336  m_cancelled( false )
337 {
338 }
339 
340 
342 {
343  stopWorkers();
344 }
345 
346 
347 void FOOTPRINT_LIST_IMPL::WriteCacheToFile( const wxString& aFilePath )
348 {
349  wxFileName tmpFileName = wxFileName::CreateTempFileName( aFilePath );
350  wxFFileOutputStream outStream( tmpFileName.GetFullPath() );
351  wxTextOutputStream txtStream( outStream );
352 
353  if( !outStream.IsOk() )
354  {
355  return;
356  }
357 
358  txtStream << wxString::Format( "%lld", m_list_timestamp ) << endl;
359 
360  for( std::unique_ptr<FOOTPRINT_INFO>& fpinfo : m_list )
361  {
362  txtStream << fpinfo->GetLibNickname() << endl;
363  txtStream << fpinfo->GetName() << endl;
364  txtStream << EscapeString( fpinfo->GetDescription(), CTX_LINE ) << endl;
365  txtStream << EscapeString( fpinfo->GetKeywords(), CTX_LINE ) << endl;
366  txtStream << wxString::Format( "%d", fpinfo->GetOrderNum() ) << endl;
367  txtStream << wxString::Format( "%u", fpinfo->GetPadCount() ) << endl;
368  txtStream << wxString::Format( "%u", fpinfo->GetUniquePadCount() ) << endl;
369  }
370 
371  txtStream.Flush();
372  outStream.Close();
373 
374  if( !wxRenameFile( tmpFileName.GetFullPath(), aFilePath, true ) )
375  {
376  // cleanup in case rename failed
377  // its also not the end of the world since this is just a cache file
378  wxRemoveFile( tmpFileName.GetFullPath() );
379  }
380 }
381 
382 
383 void FOOTPRINT_LIST_IMPL::ReadCacheFromFile( const wxString& aFilePath )
384 {
385  wxTextFile cacheFile( aFilePath );
386 
387  m_list_timestamp = 0;
388  m_list.clear();
389 
390  try
391  {
392  if( cacheFile.Exists() && cacheFile.Open() )
393  {
394  cacheFile.GetFirstLine().ToLongLong( &m_list_timestamp );
395 
396  while( cacheFile.GetCurrentLine() + 6 < cacheFile.GetLineCount() )
397  {
398  wxString libNickname = cacheFile.GetNextLine();
399  wxString name = cacheFile.GetNextLine();
400  wxString desc = UnescapeString( cacheFile.GetNextLine() );
401  wxString keywords = UnescapeString( cacheFile.GetNextLine() );
402  int orderNum = wxAtoi( cacheFile.GetNextLine() );
403  unsigned int padCount = (unsigned) wxAtoi( cacheFile.GetNextLine() );
404  unsigned int uniquePadCount = (unsigned) wxAtoi( cacheFile.GetNextLine() );
405 
406  FOOTPRINT_INFO_IMPL* fpinfo = new FOOTPRINT_INFO_IMPL( libNickname, name, desc,
407  keywords, orderNum,
408  padCount, uniquePadCount );
409 
410  m_list.emplace_back( std::unique_ptr<FOOTPRINT_INFO>( fpinfo ) );
411  }
412  }
413  }
414  catch( ... )
415  {
416  // whatever went wrong, invalidate the cache
417  m_list_timestamp = 0;
418  }
419 
420  // Sanity check: an empty list is very unlikely to be correct.
421  if( m_list.size() == 0 )
422  m_list_timestamp = 0;
423 
424  if( cacheFile.IsOpened() )
425  cacheFile.Close();
426 }
void push(T const &aValue)
Push a value onto the queue.
Definition: sync_queue.h:41
const wxString & GetDescription() const
Definition: footprint.h:197
virtual void SetMaxProgress(int aMaxProgress)=0
Fix the value that gives the 100 percent progress bar length (inside the current virtual zone).
SYNC_QUEUE< wxString > m_queue_out
virtual void Report(const wxString &aMessage)=0
Display aMessage in the progress bar dialog.
bool pop(T &aReceiver)
Pop a value if the queue into the provided variable.
Definition: sync_queue.h:63
unsigned GetPadCount(INCLUDE_NPTH_T aIncludeNPTH=INCLUDE_NPTH_T(INCLUDE_NPTH)) const
Return the number of pads.
Definition: footprint.cpp:1067
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition: locale_io.h:40
A progress reporter interface for use in multi-threaded environments.
std::atomic_bool m_cancelled
ERRLIST m_errors
some can be PARSE_ERRORs also
void loader_job()
Load footprints from m_queue_in.
FP_LIB_TABLE * m_lib_table
no ownership
Synchronized, locking queue.
Definition: sync_queue.h:31
Object used to populate a FOOTPRINT_LIST asynchronously.
unsigned m_pad_count
Number of pads.
std::vector< std::thread > m_threads
FOOTPRINT_LIST * m_owner
provides access to FP_LIB_TABLE
void move_push(T &&aValue)
Move a value onto the queue.
Definition: sync_queue.h:50
virtual void AdvanceProgress()=0
Increment the progress bar length (inside the current virtual zone).
SYNC_QUEUE< wxString > m_queue_in
long long GenerateTimestamp(const wxString *aNickname)
Generate a hashed timestamp representing the last-mod-times of the library indicated by aNickname,...
wxString m_doc
Footprint description.
void FootprintEnumerate(wxArrayString &aFootprintNames, const wxString &aNickname, bool aBestEfforts)
Return a list of footprint names contained within the library given by aNickname.
PROGRESS_REPORTER * m_progress_reporter
virtual void AdvancePhase()=0
Use the next available virtual zone of the dialog progress bar.
bool empty() const
Return true if the queue is empty.
Definition: sync_queue.h:82
void startWorkers(FP_LIB_TABLE *aTable, const wxString *aNickname, FOOTPRINT_ASYNC_LOADER *aLoader, unsigned aNThreads) override
Launch worker threads to load footprints.
wxString m_fpname
Module name.
Definition of file extensions used in Kicad.
#define _(s)
void PrefetchLib(const wxString &aNickname)
If possible, prefetches the specified library (e.g.
std::atomic_size_t m_count_finished
bool Join()
Wait until the worker threads are finished, and then perform any required single-threaded finishing o...
bool CatchErrors(const std::function< void()> &aFunc)
Call aFunc, pushing any IO_ERRORs and std::exceptions it throws onto m_errors.
const wxString & GetKeywords() const
Definition: footprint.h:200
void stopWorkers() override
Stop worker threads.
wxString m_keywords
Footprint keywords.
wxString UnescapeString(const wxString &aSource)
const FOOTPRINT * GetEnumeratedFootprint(const wxString &aNickname, const wxString &aFootprintName)
A version of FootprintLoad() for use after FootprintEnumerate() for more efficient cache management.
FOOTPRINT_ASYNC_LOADER * m_loader
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
size_t size() const
Return the size of the queue.
Definition: sync_queue.h:91
const char * name
Definition: DXF_plotter.cpp:56
unsigned GetUniquePadCount(INCLUDE_NPTH_T aIncludeNPTH=INCLUDE_NPTH_T(INCLUDE_NPTH)) const
Return the number of unique non-blank pads.
Definition: footprint.cpp:1086
void Start(FP_LIB_TABLE *aTable, const wxString *aNickname=nullptr, unsigned aNThreads=DEFAULT_THREADS)
Launch the worker threads.
bool ReadFootprintFiles(FP_LIB_TABLE *aTable, const wxString *aNickname=nullptr, PROGRESS_REPORTER *aProgressReporter=nullptr) override
Read all the footprints provided by the combination of aTable and aNickname.
void ReadCacheFromFile(const wxString &aFilePath) override
FP_LIB_TABLE * GetTable() const
virtual bool KeepRefreshing(bool aWait=false)=0
Update the UI (if any).
void WriteCacheToFile(const wxString &aFilePath) override
wxString EscapeString(const wxString &aSource, ESCAPE_CONTEXT aContext)
The Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which are:...
wxString m_nickname
library as known in FP_LIB_TABLE
virtual void load() override
lazily load stuff not filled in by constructor. This may throw IO_ERRORS.
void SetList(FOOTPRINT_LIST *aList)
Assign a FOOTPRINT_LIST to the loader.
Hold an error message and may be used when throwing exceptions containing meaningful error messages.
Definition: ki_exception.h:75
void clear()
Clear the queue.
Definition: sync_queue.h:100
#define THROW_IO_ERROR(msg)
Definition: ki_exception.h:38
unsigned m_unique_pad_count
Number of unique pads.
std::vector< wxString > GetLogicalLibs()
Return the logical library names, all of them that are pertinent to a look up done on this LIB_TABLE.
bool joinWorkers() override
Join worker threads.
void Abort()
Safely stop the current process.