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