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 <io_mgr.h>
30 #include <kicad_string.h>
31 #include <locale_io.h>
32 #include <kiface_ids.h>
33 #include <kiway.h>
34 #include <lib_id.h>
37 #include <wx/textfile.h>
38 #include <wx/txtstrm.h>
39 #include <wx/wfstream.h>
40 
41 #include <thread>
42 #include <mutex>
43 
44 
46 {
47  FP_LIB_TABLE* fptable = m_owner->GetTable();
48 
49  wxASSERT( fptable );
50 
51  const FOOTPRINT* footprint = fptable->GetEnumeratedFootprint( m_nickname, m_fpname );
52 
53  if( footprint == NULL ) // Should happen only with malformed/broken libraries
54  {
55  m_pad_count = 0;
57  }
58  else
59  {
62  m_keywords = footprint->GetKeywords();
63  m_doc = footprint->GetDescription();
64  }
65 
66  m_loaded = true;
67 }
68 
69 
70 bool FOOTPRINT_LIST_IMPL::CatchErrors( const std::function<void()>& aFunc )
71 {
72  try
73  {
74  aFunc();
75  }
76  catch( const IO_ERROR& ioe )
77  {
78  m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
79  return false;
80  }
81  catch( const std::exception& se )
82  {
83  // This is a round about way to do this, but who knows what THROW_IO_ERROR()
84  // may be tricked out to do someday, keep it in the game.
85  try
86  {
87  THROW_IO_ERROR( se.what() );
88  }
89  catch( const IO_ERROR& ioe )
90  {
91  m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
92  }
93  return false;
94  }
95 
96  return true;
97 }
98 
99 
101 {
102  wxString nickname;
103 
104  while( m_queue_in.pop( nickname ) && !m_cancelled )
105  {
106  CatchErrors( [this, &nickname]() {
107  m_lib_table->PrefetchLib( nickname );
108  m_queue_out.push( nickname );
109  } );
110 
111  m_count_finished.fetch_add( 1 );
112 
113  if( m_progress_reporter )
115  }
116 }
117 
118 
119 bool FOOTPRINT_LIST_IMPL::ReadFootprintFiles( FP_LIB_TABLE* aTable, const wxString* aNickname,
120  PROGRESS_REPORTER* aProgressReporter )
121 {
122  long long int generatedTimestamp = aTable->GenerateTimestamp( aNickname );
123 
124  if( generatedTimestamp == m_list_timestamp )
125  return true;
126 
127  m_progress_reporter = aProgressReporter;
128 
129  if( m_progress_reporter )
130  {
132  m_progress_reporter->Report( _( "Fetching Footprint Libraries" ) );
133  }
134 
135  m_cancelled = false;
136 
137  FOOTPRINT_ASYNC_LOADER loader;
138 
139  loader.SetList( this );
140  loader.Start( aTable, aNickname );
141 
142 
143  while( !m_cancelled && (int)m_count_finished.load() < m_loader->m_total_libs )
144  {
146  m_cancelled = true;
147 
148  wxMilliSleep( 20 );
149  }
150 
151  if( m_cancelled )
152  {
153  loader.Abort();
154  }
155  else
156  {
157  if( m_progress_reporter )
158  {
161  m_progress_reporter->Report( _( "Loading Footprints" ) );
162  }
163 
164  loader.Join();
165 
166  if( m_progress_reporter )
168  }
169 
170  if( m_cancelled )
171  m_list_timestamp = 0; // God knows what we got before we were cancelled
172  else
173  m_list_timestamp = generatedTimestamp;
174 
175  return m_errors.empty();
176 }
177 
178 
179 void FOOTPRINT_LIST_IMPL::startWorkers( FP_LIB_TABLE* aTable, wxString const* aNickname,
180  FOOTPRINT_ASYNC_LOADER* aLoader, unsigned aNThreads )
181 {
182  m_loader = aLoader;
183  m_lib_table = aTable;
184 
185  // Clear data before reading files
186  m_count_finished.store( 0 );
187  m_errors.clear();
188  m_list.clear();
189  m_threads.clear();
190  m_queue_in.clear();
191  m_queue_out.clear();
192 
193  if( aNickname )
194  m_queue_in.push( *aNickname );
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 
210 {
211  std::lock_guard<std::mutex> lock1( m_join );
212 
213  // To safely stop our workers, we set the cancellation flag (they will each
214  // exit on their next safe loop location when this is set). Then we need to wait
215  // for all threads to finish as closing the implementation will free the queues
216  // that the threads write to.
217  for( auto& i : m_threads )
218  i.join();
219 
220  m_threads.clear();
221  m_queue_in.clear();
222  m_count_finished.store( 0 );
223 
224  // If we have cancelled in the middle of a load, clear our timestamp to re-load next time
225  if( m_cancelled )
226  m_list_timestamp = 0;
227 }
228 
230 {
231  {
232  std::lock_guard<std::mutex> lock1( m_join );
233 
234  for( auto& i : m_threads )
235  i.join();
236 
237  m_threads.clear();
238  m_queue_in.clear();
239  m_count_finished.store( 0 );
240  }
241 
242  size_t total_count = m_queue_out.size();
243 
244  LOCALE_IO toggle_locale;
245 
246  // Parse the footprints in parallel. WARNING! This requires changing the locale, which is
247  // GLOBAL. It is only threadsafe to construct the LOCALE_IO before the threads are created,
248  // destroy it after they finish, and block the main (GUI) thread while they work. Any deviation
249  // from this will cause nasal demons.
250  //
251  // TODO: blast LOCALE_IO into the sun
252 
254  std::vector<std::thread> threads;
255 
256  for( size_t ii = 0; ii < std::thread::hardware_concurrency() + 1; ++ii )
257  {
258  threads.emplace_back( [this, &queue_parsed]() {
259  wxString nickname;
260 
261  while( m_queue_out.pop( nickname ) && !m_cancelled )
262  {
263  wxArrayString fpnames;
264 
265  try
266  {
267  m_lib_table->FootprintEnumerate( fpnames, nickname, false );
268  }
269  catch( const IO_ERROR& ioe )
270  {
271  m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
272  }
273  catch( const std::exception& se )
274  {
275  // This is a round about way to do this, but who knows what THROW_IO_ERROR()
276  // may be tricked out to do someday, keep it in the game.
277  try
278  {
279  THROW_IO_ERROR( se.what() );
280  }
281  catch( const IO_ERROR& ioe )
282  {
283  m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
284  }
285  }
286 
287  for( unsigned jj = 0; jj < fpnames.size() && !m_cancelled; ++jj )
288  {
289  wxString fpname = fpnames[jj];
290  FOOTPRINT_INFO* fpinfo = new FOOTPRINT_INFO_IMPL( this, nickname, fpname );
291  queue_parsed.move_push( std::unique_ptr<FOOTPRINT_INFO>( fpinfo ) );
292  }
293 
294  if( m_progress_reporter )
296 
297  m_count_finished.fetch_add( 1 );
298  }
299  } );
300  }
301 
302  while( !m_cancelled && (size_t)m_count_finished.load() < total_count )
303  {
305  m_cancelled = true;
306 
307  wxMilliSleep( 30 );
308  }
309 
310  for( auto& thr : threads )
311  thr.join();
312 
313  std::unique_ptr<FOOTPRINT_INFO> fpi;
314 
315  while( queue_parsed.pop( fpi ) )
316  m_list.push_back( std::move( fpi ) );
317 
318  std::sort( m_list.begin(), m_list.end(), []( std::unique_ptr<FOOTPRINT_INFO> const& lhs,
319  std::unique_ptr<FOOTPRINT_INFO> const& rhs ) -> bool
320  {
321  return *lhs < *rhs;
322  } );
323 
324  return m_errors.empty();
325 }
326 
327 
329  m_loader( nullptr ),
330  m_count_finished( 0 ),
331  m_list_timestamp( 0 ),
332  m_progress_reporter( nullptr ),
333  m_cancelled( false )
334 {
335 }
336 
337 
339 {
340  stopWorkers();
341 }
342 
343 
344 void FOOTPRINT_LIST_IMPL::WriteCacheToFile( const wxString& aFilePath )
345 {
346  wxFileName tmpFileName = wxFileName::CreateTempFileName( aFilePath );
347  wxFFileOutputStream outStream( tmpFileName.GetFullPath() );
348  wxTextOutputStream txtStream( outStream );
349 
350  if( !outStream.IsOk() )
351  {
352  return;
353  }
354 
355  txtStream << wxString::Format( "%lld", m_list_timestamp ) << endl;
356 
357  for( std::unique_ptr<FOOTPRINT_INFO>& fpinfo : m_list )
358  {
359  txtStream << fpinfo->GetLibNickname() << endl;
360  txtStream << fpinfo->GetName() << endl;
361  txtStream << EscapeString( fpinfo->GetDescription(), CTX_LINE ) << endl;
362  txtStream << EscapeString( fpinfo->GetKeywords(), CTX_LINE ) << endl;
363  txtStream << wxString::Format( "%d", fpinfo->GetOrderNum() ) << endl;
364  txtStream << wxString::Format( "%u", fpinfo->GetPadCount() ) << endl;
365  txtStream << wxString::Format( "%u", fpinfo->GetUniquePadCount() ) << endl;
366  }
367 
368  txtStream.Flush();
369  outStream.Close();
370 
371  if( !wxRenameFile( tmpFileName.GetFullPath(), aFilePath, true ) )
372  {
373  // cleanup in case rename failed
374  // its also not the end of the world since this is just a cache file
375  wxRemoveFile( tmpFileName.GetFullPath() );
376  }
377 }
378 
379 
380 void FOOTPRINT_LIST_IMPL::ReadCacheFromFile( const wxString& aFilePath )
381 {
382  wxTextFile cacheFile( aFilePath );
383 
384  m_list_timestamp = 0;
385  m_list.clear();
386 
387  try
388  {
389  if( cacheFile.Exists() && cacheFile.Open() )
390  {
391  cacheFile.GetFirstLine().ToLongLong( &m_list_timestamp );
392 
393  while( cacheFile.GetCurrentLine() + 6 < cacheFile.GetLineCount() )
394  {
395  wxString libNickname = cacheFile.GetNextLine();
396  wxString name = cacheFile.GetNextLine();
397  wxString desc = UnescapeString( cacheFile.GetNextLine() );
398  wxString keywords = UnescapeString( cacheFile.GetNextLine() );
399  int orderNum = wxAtoi( cacheFile.GetNextLine() );
400  unsigned int padCount = (unsigned) wxAtoi( cacheFile.GetNextLine() );
401  unsigned int uniquePadCount = (unsigned) wxAtoi( cacheFile.GetNextLine() );
402 
403  FOOTPRINT_INFO_IMPL* fpinfo = new FOOTPRINT_INFO_IMPL( libNickname, name, desc,
404  keywords, orderNum,
405  padCount, uniquePadCount );
406 
407  m_list.emplace_back( std::unique_ptr<FOOTPRINT_INFO>( fpinfo ) );
408  }
409  }
410  }
411  catch( ... )
412  {
413  // whatever went wrong, invalidate the cache
414  m_list_timestamp = 0;
415  }
416 
417  // Sanity check: an empty list is very unlikely to be correct.
418  if( m_list.size() == 0 )
419  m_list_timestamp = 0;
420 
421  if( cacheFile.IsOpened() )
422  cacheFile.Close();
423 }
void push(T const &aValue)
Push a value onto the queue.
Definition: sync_queue.h:41
virtual void AdvancePhase()
Uses the next available virtual zone of the dialog progress bar.
const wxString & GetDescription() const
Definition: footprint.h:188
SYNC_QUEUE< wxString > m_queue_out
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:1018
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition: locale_io.h:40
A progress reporter 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
virtual void Report(const wxString &aMessage)
Display aMessage in the progress bar dialog.
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
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
#define NULL
void Start(FP_LIB_TABLE *aTable, wxString const *aNickname=nullptr, unsigned aNThreads=DEFAULT_THREADS)
Launch the worker threads.
bool empty() const
Return true if the queue is empty.
Definition: sync_queue.h:82
wxString m_fpname
Module name.
Definition of file extensions used in Kicad.
void startWorkers(FP_LIB_TABLE *aTable, wxString const *aNickname, FOOTPRINT_ASYNC_LOADER *aLoader, unsigned aNThreads) override
Launch worker threads to load footprints.
#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:191
void stopWorkers() override
Stop worker threads.
wxString m_keywords
Footprint keywords.
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:59
wxString UnescapeString(const wxString &aSource)
Definition: string.cpp:214
wxString EscapeString(const wxString &aSource, ESCAPE_CONTEXT aContext)
The Escape/Unescape routines use HTML-entity-reference-style encoding to handle characters which are:...
Definition: string.cpp:141
bool KeepRefreshing(bool aWait=false)
Update the UI dialog.
unsigned GetUniquePadCount(INCLUDE_NPTH_T aIncludeNPTH=INCLUDE_NPTH_T(INCLUDE_NPTH)) const
Return the number of unique non-blank pads.
Definition: footprint.cpp:1037
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
void WriteCacheToFile(const wxString &aFilePath) override
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 SetMaxProgress(int aMaxProgress)
Fix the value thar gives the 100 percent progress bar length (inside the current virtual zone)
void AdvanceProgress()
Increment the progress bar length (inside the current virtual zone)
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.