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.

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