2017-03-23 00:59:25 +00:00
|
|
|
/*
|
|
|
|
* 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>
|
2019-08-07 04:26:33 +00:00
|
|
|
* Copyright (C) 1992-2019 KiCad Developers, see AUTHORS.txt for contributors.
|
2017-03-23 00:59:25 +00:00
|
|
|
*
|
|
|
|
* 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 <class_module.h>
|
|
|
|
#include <common.h>
|
|
|
|
#include <fctsys.h>
|
|
|
|
#include <footprint_info.h>
|
|
|
|
#include <fp_lib_table.h>
|
|
|
|
#include <html_messagebox.h>
|
|
|
|
#include <io_mgr.h>
|
|
|
|
#include <kiface_ids.h>
|
|
|
|
#include <kiway.h>
|
|
|
|
#include <lib_id.h>
|
|
|
|
#include <macros.h>
|
|
|
|
#include <pgm_base.h>
|
|
|
|
#include <wildcards_and_files_ext.h>
|
2018-02-10 13:32:57 +00:00
|
|
|
#include <widgets/progress_reporter.h>
|
2017-03-23 00:59:25 +00:00
|
|
|
|
|
|
|
#include <thread>
|
2018-06-04 20:44:43 +00:00
|
|
|
#include <mutex>
|
2017-03-23 00:59:25 +00:00
|
|
|
|
|
|
|
|
|
|
|
void FOOTPRINT_INFO_IMPL::load()
|
|
|
|
{
|
|
|
|
FP_LIB_TABLE* fptable = m_owner->GetTable();
|
|
|
|
|
|
|
|
wxASSERT( fptable );
|
|
|
|
|
2018-07-30 13:18:37 +00:00
|
|
|
const MODULE* footprint = fptable->GetEnumeratedFootprint( m_nickname, m_fpname );
|
|
|
|
|
|
|
|
if( footprint == NULL ) // Should happen only with malformed/broken libraries
|
2017-03-23 00:59:25 +00:00
|
|
|
{
|
|
|
|
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();
|
|
|
|
}
|
2018-07-30 13:18:37 +00:00
|
|
|
|
|
|
|
m_loaded = true;
|
2017-03-23 00:59:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-09-23 09:20:10 +00:00
|
|
|
bool FOOTPRINT_LIST_IMPL::CatchErrors( const std::function<void()>& aFunc )
|
2017-03-23 00:59:25 +00:00
|
|
|
{
|
|
|
|
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()
|
|
|
|
{
|
2017-03-24 14:09:08 +00:00
|
|
|
wxString nickname;
|
|
|
|
|
2018-02-10 13:32:57 +00:00
|
|
|
while( m_queue_in.pop( nickname ) && !m_cancelled )
|
2017-03-23 00:59:25 +00:00
|
|
|
{
|
|
|
|
CatchErrors( [this, &nickname]() {
|
2017-03-24 14:09:08 +00:00
|
|
|
m_lib_table->PrefetchLib( nickname );
|
|
|
|
m_queue_out.push( nickname );
|
2017-03-23 00:59:25 +00:00
|
|
|
} );
|
|
|
|
|
|
|
|
m_count_finished.fetch_add( 1 );
|
|
|
|
|
2018-02-10 13:32:57 +00:00
|
|
|
if( m_progress_reporter )
|
|
|
|
m_progress_reporter->AdvanceProgress();
|
2017-03-23 00:59:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-02-10 13:32:57 +00:00
|
|
|
bool FOOTPRINT_LIST_IMPL::ReadFootprintFiles( FP_LIB_TABLE* aTable, const wxString* aNickname,
|
2018-04-16 22:18:02 +00:00
|
|
|
PROGRESS_REPORTER* aProgressReporter )
|
2017-03-23 00:59:25 +00:00
|
|
|
{
|
2018-07-30 13:18:37 +00:00
|
|
|
long long int generatedTimestamp = aTable->GenerateTimestamp( aNickname );
|
|
|
|
|
|
|
|
if( generatedTimestamp == m_list_timestamp )
|
2018-02-05 20:55:00 +00:00
|
|
|
return true;
|
|
|
|
|
2018-02-10 13:32:57 +00:00
|
|
|
m_progress_reporter = aProgressReporter;
|
2020-08-09 10:55:00 +00:00
|
|
|
|
|
|
|
if( m_progress_reporter )
|
|
|
|
{
|
|
|
|
m_progress_reporter->SetMaxProgress( m_queue_in.size() );
|
|
|
|
m_progress_reporter->Report( _( "Fetching Footprint Libraries" ) );
|
|
|
|
}
|
|
|
|
|
2018-02-10 13:32:57 +00:00
|
|
|
m_cancelled = false;
|
|
|
|
|
2017-03-23 00:59:25 +00:00
|
|
|
FOOTPRINT_ASYNC_LOADER loader;
|
|
|
|
|
|
|
|
loader.SetList( this );
|
|
|
|
loader.Start( aTable, aNickname );
|
2018-02-10 13:32:57 +00:00
|
|
|
|
|
|
|
|
2018-04-17 14:05:15 +00:00
|
|
|
while( !m_cancelled && (int)m_count_finished.load() < m_loader->m_total_libs )
|
2018-02-10 13:32:57 +00:00
|
|
|
{
|
2018-06-02 21:51:25 +00:00
|
|
|
if( m_progress_reporter && !m_progress_reporter->KeepRefreshing() )
|
|
|
|
m_cancelled = true;
|
2018-06-04 20:44:43 +00:00
|
|
|
|
|
|
|
wxMilliSleep( 20 );
|
2018-02-10 13:32:57 +00:00
|
|
|
}
|
|
|
|
|
2018-05-23 15:52:48 +00:00
|
|
|
if( m_cancelled )
|
|
|
|
{
|
|
|
|
loader.Abort();
|
|
|
|
}
|
|
|
|
else
|
2018-02-10 13:32:57 +00:00
|
|
|
{
|
|
|
|
if( m_progress_reporter )
|
|
|
|
{
|
|
|
|
m_progress_reporter->SetMaxProgress( m_queue_out.size() );
|
2020-08-09 10:55:00 +00:00
|
|
|
m_progress_reporter->AdvancePhase();
|
2018-02-10 13:32:57 +00:00
|
|
|
m_progress_reporter->Report( _( "Loading Footprints" ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
loader.Join();
|
|
|
|
|
2018-05-23 15:52:48 +00:00
|
|
|
if( m_progress_reporter )
|
|
|
|
m_progress_reporter->AdvancePhase();
|
2018-02-10 13:32:57 +00:00
|
|
|
}
|
2018-02-05 20:55:00 +00:00
|
|
|
|
2018-07-30 13:18:37 +00:00
|
|
|
if( m_cancelled )
|
|
|
|
m_list_timestamp = 0; // God knows what we got before we were cancelled
|
|
|
|
else
|
|
|
|
m_list_timestamp = generatedTimestamp;
|
|
|
|
|
2018-02-10 13:32:57 +00:00
|
|
|
return m_errors.empty();
|
2017-03-23 00:59:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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 )
|
|
|
|
{
|
2019-12-05 15:41:21 +00:00
|
|
|
m_threads.emplace_back( &FOOTPRINT_LIST_IMPL::loader_job, this );
|
2017-03-23 00:59:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-23 15:52:48 +00:00
|
|
|
void FOOTPRINT_LIST_IMPL::StopWorkers()
|
|
|
|
{
|
2018-06-04 20:44:43 +00:00
|
|
|
std::lock_guard<std::mutex> lock1( m_join );
|
|
|
|
|
2018-05-23 15:52:48 +00:00
|
|
|
// 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 );
|
2018-06-04 20:44:43 +00:00
|
|
|
|
|
|
|
// 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;
|
2018-05-23 15:52:48 +00:00
|
|
|
}
|
|
|
|
|
2017-03-23 00:59:25 +00:00
|
|
|
bool FOOTPRINT_LIST_IMPL::JoinWorkers()
|
|
|
|
{
|
2018-06-04 20:44:43 +00:00
|
|
|
{
|
|
|
|
std::lock_guard<std::mutex> lock1( m_join );
|
2017-03-23 00:59:25 +00:00
|
|
|
|
2018-06-04 20:44:43 +00:00
|
|
|
for( auto& i : m_threads )
|
|
|
|
i.join();
|
|
|
|
|
|
|
|
m_threads.clear();
|
|
|
|
m_queue_in.clear();
|
|
|
|
m_count_finished.store( 0 );
|
|
|
|
}
|
2018-02-10 13:32:57 +00:00
|
|
|
|
|
|
|
size_t total_count = m_queue_out.size();
|
2017-03-23 00:59:25 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2018-02-16 19:26:55 +00:00
|
|
|
for( size_t ii = 0; ii < std::thread::hardware_concurrency() + 1; ++ii )
|
2017-03-23 00:59:25 +00:00
|
|
|
{
|
2019-12-05 15:41:21 +00:00
|
|
|
threads.emplace_back( [this, &queue_parsed]() {
|
2017-03-24 14:09:08 +00:00
|
|
|
wxString nickname;
|
|
|
|
|
2018-02-10 13:32:57 +00:00
|
|
|
while( this->m_queue_out.pop( nickname ) && !m_cancelled )
|
2017-03-23 00:59:25 +00:00
|
|
|
{
|
2017-06-11 20:20:44 +00:00
|
|
|
wxArrayString fpnames;
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
2019-08-31 14:18:27 +00:00
|
|
|
m_lib_table->FootprintEnumerate( fpnames, nickname, false );
|
2017-06-11 20:20:44 +00:00
|
|
|
}
|
|
|
|
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 )
|
2017-03-23 00:59:25 +00:00
|
|
|
{
|
2017-06-11 20:20:44 +00:00
|
|
|
m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
|
2017-03-23 00:59:25 +00:00
|
|
|
}
|
2017-06-11 20:20:44 +00:00
|
|
|
}
|
|
|
|
|
2018-02-16 19:26:55 +00:00
|
|
|
for( unsigned jj = 0; jj < fpnames.size() && !m_cancelled; ++jj )
|
2017-06-11 20:20:44 +00:00
|
|
|
{
|
2018-02-16 19:26:55 +00:00
|
|
|
wxString fpname = fpnames[jj];
|
2017-06-11 20:20:44 +00:00
|
|
|
FOOTPRINT_INFO* fpinfo = new FOOTPRINT_INFO_IMPL( this, nickname, fpname );
|
|
|
|
queue_parsed.move_push( std::unique_ptr<FOOTPRINT_INFO>( fpinfo ) );
|
|
|
|
}
|
2018-02-10 13:32:57 +00:00
|
|
|
|
|
|
|
if( m_progress_reporter )
|
|
|
|
m_progress_reporter->AdvanceProgress();
|
|
|
|
|
|
|
|
m_count_finished.fetch_add( 1 );
|
2017-03-23 00:59:25 +00:00
|
|
|
}
|
2019-12-05 15:41:21 +00:00
|
|
|
} );
|
2017-03-23 00:59:25 +00:00
|
|
|
}
|
|
|
|
|
2018-04-17 14:05:15 +00:00
|
|
|
while( !m_cancelled && (size_t)m_count_finished.load() < total_count )
|
2018-02-10 13:32:57 +00:00
|
|
|
{
|
2018-06-02 21:51:25 +00:00
|
|
|
if( m_progress_reporter && !m_progress_reporter->KeepRefreshing() )
|
|
|
|
m_cancelled = true;
|
2018-06-04 20:44:43 +00:00
|
|
|
|
2018-07-30 13:18:37 +00:00
|
|
|
wxMilliSleep( 30 );
|
2018-02-10 13:32:57 +00:00
|
|
|
}
|
|
|
|
|
2017-03-23 00:59:25 +00:00
|
|
|
for( auto& thr : threads )
|
|
|
|
thr.join();
|
|
|
|
|
2017-03-24 14:09:08 +00:00
|
|
|
std::unique_ptr<FOOTPRINT_INFO> fpi;
|
|
|
|
|
|
|
|
while( queue_parsed.pop( fpi ) )
|
|
|
|
m_list.push_back( std::move( fpi ) );
|
2017-03-23 00:59:25 +00:00
|
|
|
|
2018-07-30 13:18:37 +00:00
|
|
|
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;
|
|
|
|
} );
|
2018-04-04 00:19:20 +00:00
|
|
|
|
2017-03-23 00:59:25 +00:00
|
|
|
return m_errors.empty();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-02-10 13:32:57 +00:00
|
|
|
FOOTPRINT_LIST_IMPL::FOOTPRINT_LIST_IMPL() :
|
|
|
|
m_loader( nullptr ),
|
|
|
|
m_count_finished( 0 ),
|
2018-02-23 12:21:06 +00:00
|
|
|
m_list_timestamp( 0 ),
|
2018-02-20 08:14:51 +00:00
|
|
|
m_progress_reporter( nullptr ),
|
|
|
|
m_cancelled( false )
|
2017-03-23 00:59:25 +00:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
FOOTPRINT_LIST_IMPL::~FOOTPRINT_LIST_IMPL()
|
|
|
|
{
|
2018-06-04 20:44:43 +00:00
|
|
|
StopWorkers();
|
2017-03-23 00:59:25 +00:00
|
|
|
}
|
2018-08-03 16:53:38 +00:00
|
|
|
|
|
|
|
|
|
|
|
void FOOTPRINT_LIST_IMPL::WriteCacheToFile( wxTextFile* aCacheFile )
|
|
|
|
{
|
|
|
|
if( aCacheFile->Exists() )
|
|
|
|
{
|
2020-01-18 01:53:43 +00:00
|
|
|
if( !aCacheFile->Open() )
|
|
|
|
return;
|
|
|
|
|
2018-08-03 16:53:38 +00:00
|
|
|
aCacheFile->Clear();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-01-18 01:53:43 +00:00
|
|
|
if( !aCacheFile->Create() )
|
|
|
|
return;
|
2018-08-03 16:53:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
aCacheFile->AddLine( wxString::Format( "%lld", m_list_timestamp ) );
|
|
|
|
|
|
|
|
for( auto& fpinfo : m_list )
|
|
|
|
{
|
|
|
|
aCacheFile->AddLine( fpinfo->GetLibNickname() );
|
|
|
|
aCacheFile->AddLine( fpinfo->GetName() );
|
2020-08-04 22:53:40 +00:00
|
|
|
aCacheFile->AddLine( EscapeString( fpinfo->GetDescription(), CTX_LINE ) );
|
|
|
|
aCacheFile->AddLine( EscapeString( fpinfo->GetKeywords(), CTX_LINE ) );
|
2018-08-03 16:53:38 +00:00
|
|
|
aCacheFile->AddLine( wxString::Format( "%d", fpinfo->GetOrderNum() ) );
|
|
|
|
aCacheFile->AddLine( wxString::Format( "%u", fpinfo->GetPadCount() ) );
|
|
|
|
aCacheFile->AddLine( wxString::Format( "%u", fpinfo->GetUniquePadCount() ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
aCacheFile->Write();
|
|
|
|
aCacheFile->Close();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void FOOTPRINT_LIST_IMPL::ReadCacheFromFile( wxTextFile* aCacheFile )
|
|
|
|
{
|
2018-11-19 21:06:02 +00:00
|
|
|
m_list_timestamp = 0;
|
|
|
|
m_list.clear();
|
|
|
|
|
2018-08-03 16:53:38 +00:00
|
|
|
try
|
|
|
|
{
|
2020-01-18 01:53:43 +00:00
|
|
|
if( aCacheFile->Exists() && aCacheFile->Open() )
|
2018-11-19 21:06:02 +00:00
|
|
|
{
|
|
|
|
aCacheFile->GetFirstLine().ToLongLong( &m_list_timestamp );
|
2018-08-03 16:53:38 +00:00
|
|
|
|
2018-11-19 21:06:02 +00:00
|
|
|
while( aCacheFile->GetCurrentLine() + 6 < aCacheFile->GetLineCount() )
|
|
|
|
{
|
|
|
|
wxString libNickname = aCacheFile->GetNextLine();
|
|
|
|
wxString name = aCacheFile->GetNextLine();
|
2018-11-20 21:43:31 +00:00
|
|
|
wxString description = UnescapeString( aCacheFile->GetNextLine() );
|
|
|
|
wxString keywords = UnescapeString( aCacheFile->GetNextLine() );
|
2018-11-19 21:06:02 +00:00
|
|
|
int orderNum = wxAtoi( aCacheFile->GetNextLine() );
|
|
|
|
unsigned int padCount = (unsigned) wxAtoi( aCacheFile->GetNextLine() );
|
|
|
|
unsigned int uniquePadCount = (unsigned) wxAtoi( aCacheFile->GetNextLine() );
|
|
|
|
|
|
|
|
auto* fpinfo = new FOOTPRINT_INFO_IMPL( libNickname, name, description, keywords,
|
|
|
|
orderNum, padCount, uniquePadCount );
|
|
|
|
m_list.emplace_back( std::unique_ptr<FOOTPRINT_INFO>( fpinfo ) );
|
|
|
|
}
|
2018-08-03 16:53:38 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
catch( ... )
|
|
|
|
{
|
|
|
|
// whatever went wrong, invalidate the cache
|
|
|
|
m_list_timestamp = 0;
|
|
|
|
}
|
|
|
|
|
2018-11-19 21:06:02 +00:00
|
|
|
// Sanity check: an empty list is very unlikely to be correct.
|
|
|
|
if( m_list.size() == 0 )
|
|
|
|
m_list_timestamp = 0;
|
|
|
|
|
2018-08-03 16:53:38 +00:00
|
|
|
if( aCacheFile->IsOpened() )
|
|
|
|
aCacheFile->Close();
|
|
|
|
}
|