|                                                                                                                                                                                                                                                                                     |  | /*
 * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2011 Jean-Pierre Charras, <jp.charras@wanadoo.fr> * Copyright (C) 2013-2016 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com> * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <footprint_info_impl.h>
#include <footprint.h>
#include <footprint_info.h>
#include <fp_lib_table.h>
#include <dialogs/html_messagebox.h>
#include <io_mgr.h>
#include <kicad_string.h>
#include <locale_io.h>
#include <kiface_ids.h>
#include <kiway.h>
#include <lib_id.h>
#include <pgm_base.h>
#include <wildcards_and_files_ext.h>
#include <widgets/progress_reporter.h>
#include <wx/txtstrm.h>
#include <wx/wfstream.h>
#include <thread>
#include <mutex>
void FOOTPRINT_INFO_IMPL::load(){    FP_LIB_TABLE* fptable = m_owner->GetTable();
    wxASSERT( fptable );
    const FOOTPRINT* footprint = fptable->GetEnumeratedFootprint( m_nickname, m_fpname );
    if( footprint == NULL ) // Should happen only with malformed/broken libraries
    {        m_pad_count = 0;        m_unique_pad_count = 0;    }    else    {        m_pad_count = footprint->GetPadCount( DO_NOT_INCLUDE_NPTH );        m_unique_pad_count = footprint->GetUniquePadCount( DO_NOT_INCLUDE_NPTH );        m_keywords = footprint->GetKeywords();        m_doc = footprint->GetDescription();    }
    m_loaded = true;}
bool FOOTPRINT_LIST_IMPL::CatchErrors( const std::function<void()>& aFunc ){    try    {        aFunc();    }    catch( const IO_ERROR& ioe )    {        m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );        return false;    }    catch( const std::exception& se )    {        // This is a round about way to do this, but who knows what THROW_IO_ERROR()
        // may be tricked out to do someday, keep it in the game.
        try        {            THROW_IO_ERROR( se.what() );        }        catch( const IO_ERROR& ioe )        {            m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );        }        return false;    }
    return true;}
void FOOTPRINT_LIST_IMPL::loader_job(){    wxString nickname;
    while( m_queue_in.pop( nickname ) && !m_cancelled )    {        CatchErrors( [this, &nickname]() {            m_lib_table->PrefetchLib( nickname );            m_queue_out.push( nickname );        } );
        m_count_finished.fetch_add( 1 );
        if( m_progress_reporter )            m_progress_reporter->AdvanceProgress();    }}
bool FOOTPRINT_LIST_IMPL::ReadFootprintFiles( FP_LIB_TABLE* aTable, const wxString* aNickname,                                              PROGRESS_REPORTER* aProgressReporter ){    long long int generatedTimestamp = aTable->GenerateTimestamp( aNickname );
    if( generatedTimestamp == m_list_timestamp )        return true;
    m_progress_reporter = aProgressReporter;
    if( m_progress_reporter )    {        m_progress_reporter->SetMaxProgress( m_queue_in.size() );        m_progress_reporter->Report( _( "Fetching Footprint Libraries" ) );    }
    m_cancelled = false;
    FOOTPRINT_ASYNC_LOADER loader;
    loader.SetList( this );    loader.Start( aTable, aNickname );
    while( !m_cancelled && (int)m_count_finished.load() < m_loader->m_total_libs )    {        if( m_progress_reporter && !m_progress_reporter->KeepRefreshing() )            m_cancelled = true;
        wxMilliSleep( 20 );    }
    if( m_cancelled )    {        loader.Abort();    }    else    {        if( m_progress_reporter )        {            m_progress_reporter->SetMaxProgress( m_queue_out.size() );            m_progress_reporter->AdvancePhase();            m_progress_reporter->Report( _( "Loading Footprints" ) );        }
        loader.Join();
        if( m_progress_reporter )            m_progress_reporter->AdvancePhase();    }
    if( m_cancelled )        m_list_timestamp = 0;       // God knows what we got before we were cancelled
    else        m_list_timestamp = generatedTimestamp;
    return m_errors.empty();}
void FOOTPRINT_LIST_IMPL::startWorkers( FP_LIB_TABLE* aTable, wxString const* aNickname,                                        FOOTPRINT_ASYNC_LOADER* aLoader, unsigned aNThreads ){    m_loader = aLoader;    m_lib_table = aTable;
    // Clear data before reading files
    m_count_finished.store( 0 );    m_errors.clear();    m_list.clear();    m_threads.clear();    m_queue_in.clear();    m_queue_out.clear();
    if( aNickname )        m_queue_in.push( *aNickname );    else    {        for( auto const& nickname : aTable->GetLogicalLibs() )            m_queue_in.push( nickname );    }
    m_loader->m_total_libs = m_queue_in.size();
    for( unsigned i = 0; i < aNThreads; ++i )    {        m_threads.emplace_back( &FOOTPRINT_LIST_IMPL::loader_job, this );    }}
void FOOTPRINT_LIST_IMPL::stopWorkers(){    std::lock_guard<std::mutex> lock1( m_join );
    // To safely stop our workers, we set the cancellation flag (they will each
    // exit on their next safe loop location when this is set).  Then we need to wait
    // for all threads to finish as closing the implementation will free the queues
    // that the threads write to.
    for( auto& i : m_threads )        i.join();
    m_threads.clear();    m_queue_in.clear();    m_count_finished.store( 0 );
    // If we have cancelled in the middle of a load, clear our timestamp to re-load next time
    if( m_cancelled )        m_list_timestamp = 0;}
bool FOOTPRINT_LIST_IMPL::joinWorkers(){    {        std::lock_guard<std::mutex> lock1( m_join );
        for( auto& i : m_threads )            i.join();
        m_threads.clear();        m_queue_in.clear();        m_count_finished.store( 0 );    }
    size_t total_count = m_queue_out.size();
    LOCALE_IO toggle_locale;
    // Parse the footprints in parallel. WARNING! This requires changing the locale, which is
    // GLOBAL. It is only threadsafe to construct the LOCALE_IO before the threads are created,
    // destroy it after they finish, and block the main (GUI) thread while they work. Any deviation
    // from this will cause nasal demons.
    //
    // TODO: blast LOCALE_IO into the sun
    SYNC_QUEUE<std::unique_ptr<FOOTPRINT_INFO>> queue_parsed;    std::vector<std::thread>                    threads;
    for( size_t ii = 0; ii < std::thread::hardware_concurrency() + 1; ++ii )    {        threads.emplace_back( [this, &queue_parsed]() {            wxString nickname;
            while( m_queue_out.pop( nickname ) && !m_cancelled )            {                wxArrayString fpnames;
                try                {                    m_lib_table->FootprintEnumerate( fpnames, nickname, false );                }                catch( const IO_ERROR& ioe )                {                    m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );                }                catch( const std::exception& se )                {                    // This is a round about way to do this, but who knows what THROW_IO_ERROR()
                    // may be tricked out to do someday, keep it in the game.
                    try                    {                        THROW_IO_ERROR( se.what() );                    }                    catch( const IO_ERROR& ioe )                    {                        m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );                    }                }
                for( unsigned jj = 0; jj < fpnames.size() && !m_cancelled; ++jj )                {                    wxString fpname = fpnames[jj];                    FOOTPRINT_INFO* fpinfo = new FOOTPRINT_INFO_IMPL( this, nickname, fpname );                    queue_parsed.move_push( std::unique_ptr<FOOTPRINT_INFO>( fpinfo ) );                }
                if( m_progress_reporter )                    m_progress_reporter->AdvanceProgress();
                m_count_finished.fetch_add( 1 );            }        } );    }
    while( !m_cancelled && (size_t)m_count_finished.load() < total_count )    {        if( m_progress_reporter && !m_progress_reporter->KeepRefreshing() )            m_cancelled = true;
        wxMilliSleep( 30 );    }
    for( auto& thr : threads )        thr.join();
    std::unique_ptr<FOOTPRINT_INFO> fpi;
    while( queue_parsed.pop( fpi ) )        m_list.push_back( std::move( fpi ) );
    std::sort( m_list.begin(), m_list.end(), []( std::unique_ptr<FOOTPRINT_INFO> const& lhs,                                                 std::unique_ptr<FOOTPRINT_INFO> const& rhs ) -> bool                                             {                                                 return *lhs < *rhs;                                             } );
    return m_errors.empty();}
FOOTPRINT_LIST_IMPL::FOOTPRINT_LIST_IMPL() :    m_loader( nullptr ),    m_count_finished( 0 ),    m_list_timestamp( 0 ),    m_progress_reporter( nullptr ),    m_cancelled( false ){}
FOOTPRINT_LIST_IMPL::~FOOTPRINT_LIST_IMPL(){    stopWorkers();}
void FOOTPRINT_LIST_IMPL::WriteCacheToFile( const wxString& aFilePath ){    wxFileName          tmpFileName = wxFileName::CreateTempFileName( aFilePath );    wxFFileOutputStream outStream( tmpFileName.GetFullPath() );    wxTextOutputStream  txtStream( outStream );
    if( !outStream.IsOk() )    {        return;    }
    txtStream << wxString::Format( "%lld", m_list_timestamp ) << endl;
    for( std::unique_ptr<FOOTPRINT_INFO>& fpinfo : m_list )    {        txtStream << fpinfo->GetLibNickname() << endl;        txtStream << fpinfo->GetName() << endl;        txtStream << EscapeString( fpinfo->GetDescription(), CTX_LINE ) << endl;        txtStream << EscapeString( fpinfo->GetKeywords(), CTX_LINE ) << endl;        txtStream << wxString::Format( "%d", fpinfo->GetOrderNum() ) << endl;        txtStream << wxString::Format( "%u", fpinfo->GetPadCount() ) << endl;        txtStream << wxString::Format( "%u", fpinfo->GetUniquePadCount() ) << endl;    }
    txtStream.Flush();    outStream.Close();
    if( !wxRenameFile( tmpFileName.GetFullPath(), aFilePath, true ) )    {        // cleanup incase rename failed
        // its also not the end of the world since this is just a cache file
        wxRemoveFile( tmpFileName.GetFullPath() );    }}
void FOOTPRINT_LIST_IMPL::ReadCacheFromFile( const wxString& aFilePath ){    wxTextFile cacheFile( aFilePath );
    m_list_timestamp = 0;    m_list.clear();
    try    {        if( cacheFile.Exists() && cacheFile.Open() )        {            cacheFile.GetFirstLine().ToLongLong( &m_list_timestamp );
            while( cacheFile.GetCurrentLine() + 6 < cacheFile.GetLineCount() )            {                wxString     libNickname    = cacheFile.GetNextLine();                wxString     name           = cacheFile.GetNextLine();                wxString     description    = UnescapeString( cacheFile.GetNextLine() );                wxString     keywords       = UnescapeString( cacheFile.GetNextLine() );                int          orderNum       = wxAtoi( cacheFile.GetNextLine() );                unsigned int padCount       = (unsigned) wxAtoi( cacheFile.GetNextLine() );                unsigned int uniquePadCount = (unsigned) wxAtoi( cacheFile.GetNextLine() );
                auto* fpinfo = new FOOTPRINT_INFO_IMPL( libNickname, name, description, keywords,                                                        orderNum, padCount, uniquePadCount );                m_list.emplace_back( std::unique_ptr<FOOTPRINT_INFO>( fpinfo ) );            }        }    }    catch( ... )    {        // whatever went wrong, invalidate the cache
        m_list_timestamp = 0;    }
    // Sanity check: an empty list is very unlikely to be correct.
    if( m_list.size() == 0 )        m_list_timestamp = 0;
    if( cacheFile.IsOpened() )        cacheFile.Close();}
 |