You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

421 lines
12 KiB

  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. #include <footprint_info_impl.h>
  22. #include <footprint.h>
  23. #include <footprint_info.h>
  24. #include <fp_lib_table.h>
  25. #include <dialogs/html_messagebox.h>
  26. #include <io_mgr.h>
  27. #include <kicad_string.h>
  28. #include <locale_io.h>
  29. #include <kiface_ids.h>
  30. #include <kiway.h>
  31. #include <lib_id.h>
  32. #include <pgm_base.h>
  33. #include <wildcards_and_files_ext.h>
  34. #include <widgets/progress_reporter.h>
  35. #include <wx/txtstrm.h>
  36. #include <wx/wfstream.h>
  37. #include <thread>
  38. #include <mutex>
  39. void FOOTPRINT_INFO_IMPL::load()
  40. {
  41. FP_LIB_TABLE* fptable = m_owner->GetTable();
  42. wxASSERT( fptable );
  43. const FOOTPRINT* footprint = fptable->GetEnumeratedFootprint( m_nickname, m_fpname );
  44. if( footprint == NULL ) // Should happen only with malformed/broken libraries
  45. {
  46. m_pad_count = 0;
  47. m_unique_pad_count = 0;
  48. }
  49. else
  50. {
  51. m_pad_count = footprint->GetPadCount( DO_NOT_INCLUDE_NPTH );
  52. m_unique_pad_count = footprint->GetUniquePadCount( DO_NOT_INCLUDE_NPTH );
  53. m_keywords = footprint->GetKeywords();
  54. m_doc = footprint->GetDescription();
  55. }
  56. m_loaded = true;
  57. }
  58. bool FOOTPRINT_LIST_IMPL::CatchErrors( const std::function<void()>& aFunc )
  59. {
  60. try
  61. {
  62. aFunc();
  63. }
  64. catch( const IO_ERROR& ioe )
  65. {
  66. m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
  67. return false;
  68. }
  69. catch( const std::exception& se )
  70. {
  71. // This is a round about way to do this, but who knows what THROW_IO_ERROR()
  72. // may be tricked out to do someday, keep it in the game.
  73. try
  74. {
  75. THROW_IO_ERROR( se.what() );
  76. }
  77. catch( const IO_ERROR& ioe )
  78. {
  79. m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
  80. }
  81. return false;
  82. }
  83. return true;
  84. }
  85. void FOOTPRINT_LIST_IMPL::loader_job()
  86. {
  87. wxString nickname;
  88. while( m_queue_in.pop( nickname ) && !m_cancelled )
  89. {
  90. CatchErrors( [this, &nickname]() {
  91. m_lib_table->PrefetchLib( nickname );
  92. m_queue_out.push( nickname );
  93. } );
  94. m_count_finished.fetch_add( 1 );
  95. if( m_progress_reporter )
  96. m_progress_reporter->AdvanceProgress();
  97. }
  98. }
  99. bool FOOTPRINT_LIST_IMPL::ReadFootprintFiles( FP_LIB_TABLE* aTable, const wxString* aNickname,
  100. PROGRESS_REPORTER* aProgressReporter )
  101. {
  102. long long int generatedTimestamp = aTable->GenerateTimestamp( aNickname );
  103. if( generatedTimestamp == m_list_timestamp )
  104. return true;
  105. m_progress_reporter = aProgressReporter;
  106. if( m_progress_reporter )
  107. {
  108. m_progress_reporter->SetMaxProgress( m_queue_in.size() );
  109. m_progress_reporter->Report( _( "Fetching Footprint Libraries" ) );
  110. }
  111. m_cancelled = false;
  112. FOOTPRINT_ASYNC_LOADER loader;
  113. loader.SetList( this );
  114. loader.Start( aTable, aNickname );
  115. while( !m_cancelled && (int)m_count_finished.load() < m_loader->m_total_libs )
  116. {
  117. if( m_progress_reporter && !m_progress_reporter->KeepRefreshing() )
  118. m_cancelled = true;
  119. wxMilliSleep( 20 );
  120. }
  121. if( m_cancelled )
  122. {
  123. loader.Abort();
  124. }
  125. else
  126. {
  127. if( m_progress_reporter )
  128. {
  129. m_progress_reporter->SetMaxProgress( m_queue_out.size() );
  130. m_progress_reporter->AdvancePhase();
  131. m_progress_reporter->Report( _( "Loading Footprints" ) );
  132. }
  133. loader.Join();
  134. if( m_progress_reporter )
  135. m_progress_reporter->AdvancePhase();
  136. }
  137. if( m_cancelled )
  138. m_list_timestamp = 0; // God knows what we got before we were cancelled
  139. else
  140. m_list_timestamp = generatedTimestamp;
  141. return m_errors.empty();
  142. }
  143. void FOOTPRINT_LIST_IMPL::startWorkers( FP_LIB_TABLE* aTable, wxString const* aNickname,
  144. FOOTPRINT_ASYNC_LOADER* aLoader, unsigned aNThreads )
  145. {
  146. m_loader = aLoader;
  147. m_lib_table = aTable;
  148. // Clear data before reading files
  149. m_count_finished.store( 0 );
  150. m_errors.clear();
  151. m_list.clear();
  152. m_threads.clear();
  153. m_queue_in.clear();
  154. m_queue_out.clear();
  155. if( aNickname )
  156. m_queue_in.push( *aNickname );
  157. else
  158. {
  159. for( auto const& nickname : aTable->GetLogicalLibs() )
  160. m_queue_in.push( nickname );
  161. }
  162. m_loader->m_total_libs = m_queue_in.size();
  163. for( unsigned i = 0; i < aNThreads; ++i )
  164. {
  165. m_threads.emplace_back( &FOOTPRINT_LIST_IMPL::loader_job, this );
  166. }
  167. }
  168. void FOOTPRINT_LIST_IMPL::stopWorkers()
  169. {
  170. std::lock_guard<std::mutex> lock1( m_join );
  171. // To safely stop our workers, we set the cancellation flag (they will each
  172. // exit on their next safe loop location when this is set). Then we need to wait
  173. // for all threads to finish as closing the implementation will free the queues
  174. // that the threads write to.
  175. for( auto& i : m_threads )
  176. i.join();
  177. m_threads.clear();
  178. m_queue_in.clear();
  179. m_count_finished.store( 0 );
  180. // If we have cancelled in the middle of a load, clear our timestamp to re-load next time
  181. if( m_cancelled )
  182. m_list_timestamp = 0;
  183. }
  184. bool FOOTPRINT_LIST_IMPL::joinWorkers()
  185. {
  186. {
  187. std::lock_guard<std::mutex> lock1( m_join );
  188. for( auto& i : m_threads )
  189. i.join();
  190. m_threads.clear();
  191. m_queue_in.clear();
  192. m_count_finished.store( 0 );
  193. }
  194. size_t total_count = m_queue_out.size();
  195. LOCALE_IO toggle_locale;
  196. // Parse the footprints in parallel. WARNING! This requires changing the locale, which is
  197. // GLOBAL. It is only threadsafe to construct the LOCALE_IO before the threads are created,
  198. // destroy it after they finish, and block the main (GUI) thread while they work. Any deviation
  199. // from this will cause nasal demons.
  200. //
  201. // TODO: blast LOCALE_IO into the sun
  202. SYNC_QUEUE<std::unique_ptr<FOOTPRINT_INFO>> queue_parsed;
  203. std::vector<std::thread> threads;
  204. for( size_t ii = 0; ii < std::thread::hardware_concurrency() + 1; ++ii )
  205. {
  206. threads.emplace_back( [this, &queue_parsed]() {
  207. wxString nickname;
  208. while( m_queue_out.pop( nickname ) && !m_cancelled )
  209. {
  210. wxArrayString fpnames;
  211. try
  212. {
  213. m_lib_table->FootprintEnumerate( fpnames, nickname, false );
  214. }
  215. catch( const IO_ERROR& ioe )
  216. {
  217. m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
  218. }
  219. catch( const std::exception& se )
  220. {
  221. // This is a round about way to do this, but who knows what THROW_IO_ERROR()
  222. // may be tricked out to do someday, keep it in the game.
  223. try
  224. {
  225. THROW_IO_ERROR( se.what() );
  226. }
  227. catch( const IO_ERROR& ioe )
  228. {
  229. m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
  230. }
  231. }
  232. for( unsigned jj = 0; jj < fpnames.size() && !m_cancelled; ++jj )
  233. {
  234. wxString fpname = fpnames[jj];
  235. FOOTPRINT_INFO* fpinfo = new FOOTPRINT_INFO_IMPL( this, nickname, fpname );
  236. queue_parsed.move_push( std::unique_ptr<FOOTPRINT_INFO>( fpinfo ) );
  237. }
  238. if( m_progress_reporter )
  239. m_progress_reporter->AdvanceProgress();
  240. m_count_finished.fetch_add( 1 );
  241. }
  242. } );
  243. }
  244. while( !m_cancelled && (size_t)m_count_finished.load() < total_count )
  245. {
  246. if( m_progress_reporter && !m_progress_reporter->KeepRefreshing() )
  247. m_cancelled = true;
  248. wxMilliSleep( 30 );
  249. }
  250. for( auto& thr : threads )
  251. thr.join();
  252. std::unique_ptr<FOOTPRINT_INFO> fpi;
  253. while( queue_parsed.pop( fpi ) )
  254. m_list.push_back( std::move( fpi ) );
  255. std::sort( m_list.begin(), m_list.end(), []( std::unique_ptr<FOOTPRINT_INFO> const& lhs,
  256. std::unique_ptr<FOOTPRINT_INFO> const& rhs ) -> bool
  257. {
  258. return *lhs < *rhs;
  259. } );
  260. return m_errors.empty();
  261. }
  262. FOOTPRINT_LIST_IMPL::FOOTPRINT_LIST_IMPL() :
  263. m_loader( nullptr ),
  264. m_count_finished( 0 ),
  265. m_list_timestamp( 0 ),
  266. m_progress_reporter( nullptr ),
  267. m_cancelled( false )
  268. {
  269. }
  270. FOOTPRINT_LIST_IMPL::~FOOTPRINT_LIST_IMPL()
  271. {
  272. stopWorkers();
  273. }
  274. void FOOTPRINT_LIST_IMPL::WriteCacheToFile( const wxString& aFilePath )
  275. {
  276. wxFileName tmpFileName = wxFileName::CreateTempFileName( aFilePath );
  277. wxFFileOutputStream outStream( tmpFileName.GetFullPath() );
  278. wxTextOutputStream txtStream( outStream );
  279. if( !outStream.IsOk() )
  280. {
  281. return;
  282. }
  283. txtStream << wxString::Format( "%lld", m_list_timestamp ) << endl;
  284. for( std::unique_ptr<FOOTPRINT_INFO>& fpinfo : m_list )
  285. {
  286. txtStream << fpinfo->GetLibNickname() << endl;
  287. txtStream << fpinfo->GetName() << endl;
  288. txtStream << EscapeString( fpinfo->GetDescription(), CTX_LINE ) << endl;
  289. txtStream << EscapeString( fpinfo->GetKeywords(), CTX_LINE ) << endl;
  290. txtStream << wxString::Format( "%d", fpinfo->GetOrderNum() ) << endl;
  291. txtStream << wxString::Format( "%u", fpinfo->GetPadCount() ) << endl;
  292. txtStream << wxString::Format( "%u", fpinfo->GetUniquePadCount() ) << endl;
  293. }
  294. txtStream.Flush();
  295. outStream.Close();
  296. if( !wxRenameFile( tmpFileName.GetFullPath(), aFilePath, true ) )
  297. {
  298. // cleanup incase rename failed
  299. // its also not the end of the world since this is just a cache file
  300. wxRemoveFile( tmpFileName.GetFullPath() );
  301. }
  302. }
  303. void FOOTPRINT_LIST_IMPL::ReadCacheFromFile( const wxString& aFilePath )
  304. {
  305. wxTextFile cacheFile( aFilePath );
  306. m_list_timestamp = 0;
  307. m_list.clear();
  308. try
  309. {
  310. if( cacheFile.Exists() && cacheFile.Open() )
  311. {
  312. cacheFile.GetFirstLine().ToLongLong( &m_list_timestamp );
  313. while( cacheFile.GetCurrentLine() + 6 < cacheFile.GetLineCount() )
  314. {
  315. wxString libNickname = cacheFile.GetNextLine();
  316. wxString name = cacheFile.GetNextLine();
  317. wxString description = UnescapeString( cacheFile.GetNextLine() );
  318. wxString keywords = UnescapeString( cacheFile.GetNextLine() );
  319. int orderNum = wxAtoi( cacheFile.GetNextLine() );
  320. unsigned int padCount = (unsigned) wxAtoi( cacheFile.GetNextLine() );
  321. unsigned int uniquePadCount = (unsigned) wxAtoi( cacheFile.GetNextLine() );
  322. auto* fpinfo = new FOOTPRINT_INFO_IMPL( libNickname, name, description, keywords,
  323. orderNum, padCount, uniquePadCount );
  324. m_list.emplace_back( std::unique_ptr<FOOTPRINT_INFO>( fpinfo ) );
  325. }
  326. }
  327. }
  328. catch( ... )
  329. {
  330. // whatever went wrong, invalidate the cache
  331. m_list_timestamp = 0;
  332. }
  333. // Sanity check: an empty list is very unlikely to be correct.
  334. if( m_list.size() == 0 )
  335. m_list_timestamp = 0;
  336. if( cacheFile.IsOpened() )
  337. cacheFile.Close();
  338. }