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>
28 #include <dialogs/html_message_box.h>
29 #include <string_utils.h>
30 #include <locale_io.h>
31 #include <kiway.h>
32 #include <lib_id.h>
33 #include <wildcards_and_files_ext.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
load()42 void FOOTPRINT_INFO_IMPL::load()
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;
53 m_unique_pad_count = 0;
54 }
55 else
56 {
57 m_pad_count = footprint->GetPadCount( DO_NOT_INCLUDE_NPTH );
58 m_unique_pad_count = footprint->GetUniquePadCount( DO_NOT_INCLUDE_NPTH );
59 m_keywords = footprint->GetKeywords();
60 m_doc = footprint->GetDescription();
61 }
62
63 m_loaded = true;
64 }
65
66
CatchErrors(const std::function<void ()> & aFunc)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
loader_job()98 void FOOTPRINT_LIST_IMPL::loader_job()
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 )
113 m_progress_reporter->AdvanceProgress();
114 }
115 }
116
117
ReadFootprintFiles(FP_LIB_TABLE * aTable,const wxString * aNickname,PROGRESS_REPORTER * aProgressReporter)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 {
130 m_progress_reporter->SetMaxProgress( m_queue_in.size() );
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 {
143 if( m_progress_reporter && !m_progress_reporter->KeepRefreshing() )
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 {
157 m_progress_reporter->SetMaxProgress( m_queue_out.size() );
158 m_progress_reporter->AdvancePhase();
159 m_progress_reporter->Report( _( "Loading footprints..." ) );
160 }
161
162 loader.Join();
163
164 if( m_progress_reporter )
165 m_progress_reporter->AdvancePhase();
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
startWorkers(FP_LIB_TABLE * aTable,wxString const * aNickname,FOOTPRINT_ASYNC_LOADER * aLoader,unsigned aNThreads)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
201 m_loader->m_total_libs = m_queue_in.size();
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
stopWorkers()210 void FOOTPRINT_LIST_IMPL::stopWorkers()
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
joinWorkers()231 bool FOOTPRINT_LIST_IMPL::joinWorkers()
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
255 SYNC_QUEUE<std::unique_ptr<FOOTPRINT_INFO>> queue_parsed;
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 )
297 m_progress_reporter->AdvanceProgress();
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 {
306 if( m_progress_reporter && !m_progress_reporter->KeepRefreshing() )
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
FOOTPRINT_LIST_IMPL()331 FOOTPRINT_LIST_IMPL::FOOTPRINT_LIST_IMPL() :
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
~FOOTPRINT_LIST_IMPL()341 FOOTPRINT_LIST_IMPL::~FOOTPRINT_LIST_IMPL()
342 {
343 stopWorkers();
344 }
345
346
WriteCacheToFile(const wxString & aFilePath)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
ReadCacheFromFile(const wxString & aFilePath)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 }
427