Performance enhancements for footprint info list.
Cache the footprint info on disk (in the project). Move timestamp-generation (and checking) to the filesystem so the above will be bullet-proof. Rewrite some wxWidgets classes for performance (see common.h).
This commit is contained in:
parent
5e8e2570ba
commit
f3f814e622
|
@ -529,3 +529,123 @@ bool std::less<wxPoint>::operator()( const wxPoint& aA, const wxPoint& aB ) cons
|
|||
return aA.x < aB.x;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
//
|
||||
// A cover of wxFileName::SetFullName() which avoids expensive calls to wxFileName::SplitPath().
|
||||
//
|
||||
void WX_FILENAME::SetFullName( const wxString& aFileNameAndExtension )
|
||||
{
|
||||
m_fullName = aFileNameAndExtension;
|
||||
|
||||
size_t dot = m_fullName.find_last_of( wxT( '.' ) );
|
||||
m_fn.SetName( m_fullName.substr( 0, dot ) );
|
||||
m_fn.SetExt( m_fullName.substr( dot + 1 ) );
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// An alernative to wxFileName::GetModificationTime() which avoids multiple calls to stat() on
|
||||
// POSIX kernels.
|
||||
//
|
||||
long long WX_FILENAME::GetTimestamp()
|
||||
{
|
||||
#ifdef __WINDOWS__
|
||||
if( m_fn.FileExists() )
|
||||
return m_fn.GetModificationTime().GetValue().GetValue();
|
||||
#else
|
||||
// By stat-ing the file ourselves we save wxWidgets from doing it three times:
|
||||
// Exists( wxFILE_EXISTS_SYMLINK ), FileExists(), and finally GetModificationTime()
|
||||
struct stat fn_stat;
|
||||
wxLstat( GetFullPath(), &fn_stat );
|
||||
|
||||
// Timestamp the source file, not the symlink
|
||||
if( S_ISLNK( fn_stat.st_mode ) ) // wxFILE_EXISTS_SYMLINK
|
||||
{
|
||||
char buffer[ PATH_MAX + 1 ];
|
||||
ssize_t pathLen = readlink( TO_UTF8( GetFullPath() ), buffer, PATH_MAX );
|
||||
|
||||
if( pathLen > 0 )
|
||||
{
|
||||
buffer[ pathLen ] = '\0';
|
||||
wxString srcPath = m_path + wxT( '/' ) + wxString::FromUTF8( buffer );
|
||||
wxLstat( srcPath, &fn_stat );
|
||||
}
|
||||
}
|
||||
|
||||
if( S_ISREG( fn_stat.st_mode ) ) // wxFileExists()
|
||||
return fn_stat.st_mtime * 1000;
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// A version of wxDir which avoids expensive calls to wxFileName::wxFileName().
|
||||
//
|
||||
WX_DIR::WX_DIR( const wxString& aDirPath ) :
|
||||
m_dirpath( aDirPath )
|
||||
{
|
||||
m_dir = NULL;
|
||||
|
||||
// throw away the trailing slashes
|
||||
size_t n = m_dirpath.length();
|
||||
|
||||
while ( n > 0 && m_dirpath[--n] == '/' )
|
||||
;
|
||||
|
||||
m_dirpath.Truncate(n + 1);
|
||||
|
||||
m_dir = opendir( m_dirpath.fn_str() );
|
||||
}
|
||||
|
||||
|
||||
bool WX_DIR::IsOpened() const
|
||||
{
|
||||
return m_dir != nullptr;
|
||||
}
|
||||
|
||||
|
||||
WX_DIR::~WX_DIR()
|
||||
{
|
||||
if ( m_dir )
|
||||
closedir( m_dir );
|
||||
}
|
||||
|
||||
|
||||
bool WX_DIR::GetFirst( wxString *filename, const wxString& filespec )
|
||||
{
|
||||
m_filespec = filespec;
|
||||
|
||||
rewinddir( m_dir );
|
||||
return GetNext( filename );
|
||||
}
|
||||
|
||||
|
||||
bool WX_DIR::GetNext(wxString *filename) const
|
||||
{
|
||||
dirent *dirEntry = NULL;
|
||||
wxString dirEntryName;
|
||||
bool matches = false;
|
||||
|
||||
while ( !matches )
|
||||
{
|
||||
dirEntry = readdir( m_dir );
|
||||
|
||||
if ( !dirEntry )
|
||||
return false;
|
||||
|
||||
#if wxUSE_UNICODE
|
||||
dirEntryName = wxString( dirEntry->d_name, *wxConvFileName );
|
||||
#else
|
||||
dirEntryName = dirEntry->d_name;
|
||||
#endif
|
||||
|
||||
matches = wxMatchWild( m_filespec, dirEntryName );
|
||||
}
|
||||
|
||||
*filename = dirEntryName;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -381,4 +381,67 @@ namespace std
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* A wrapper around a wxFileName which is much more performant with a (very) limited API.
|
||||
*/
|
||||
class WX_FILENAME
|
||||
{
|
||||
public:
|
||||
WX_FILENAME( const wxString& aPath, const wxString& aFilename ) :
|
||||
m_fn( aPath, aFilename ),
|
||||
m_path( aPath ),
|
||||
m_fullName( aFilename )
|
||||
{ }
|
||||
|
||||
// Avoid wxFileName's expensive path concatenation.
|
||||
wxString GetFullPath() { return m_path + wxT( '/' ) + m_fullName; }
|
||||
|
||||
wxString GetName() { return m_fn.GetName(); }
|
||||
|
||||
// Avoid wxFileName's expensive calls to wxFileName::SplitPath().
|
||||
void SetFullName( const wxString& aFileNameAndExtension );
|
||||
|
||||
// Avoid multiple calls to stat() on POSIX kernels.
|
||||
long long GetTimestamp();
|
||||
|
||||
operator wxFileName() const { return m_fn; }
|
||||
|
||||
private:
|
||||
wxFileName m_fn;
|
||||
wxString m_path;
|
||||
wxString m_fullName;
|
||||
};
|
||||
|
||||
|
||||
#ifdef __WINDOWS__
|
||||
#define WX_DIR wxDir
|
||||
#else
|
||||
// For POSIX kernels we implement our own version which avoids expensive calls to
|
||||
// wxFileName::wxFileName().
|
||||
class WX_DIR
|
||||
{
|
||||
public:
|
||||
WX_DIR( const wxString& dir );
|
||||
~WX_DIR();
|
||||
|
||||
// returns true if the directory was successfully opened
|
||||
bool IsOpened() const;
|
||||
|
||||
bool GetFirst( wxString *filename, const wxString& filespec );
|
||||
|
||||
// get next file in the enumeration started with GetFirst()
|
||||
bool GetNext( wxString *filename ) const;
|
||||
|
||||
private:
|
||||
DIR* m_dir;
|
||||
|
||||
wxString m_dirpath;
|
||||
wxString m_filespec;
|
||||
|
||||
wxDECLARE_NO_COPY_CLASS( WX_DIR );
|
||||
};
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
#endif // INCLUDE__COMMON_H_
|
||||
|
|
|
@ -145,7 +145,7 @@ protected:
|
|||
}
|
||||
|
||||
/// lazily load stuff not filled in by constructor. This may throw IO_ERRORS.
|
||||
virtual void load() = 0;
|
||||
virtual void load() { };
|
||||
|
||||
FOOTPRINT_LIST* m_owner; ///< provides access to FP_LIB_TABLE
|
||||
|
||||
|
@ -193,9 +193,6 @@ protected:
|
|||
FPILIST m_list;
|
||||
ERRLIST m_errors; ///< some can be PARSE_ERRORs also
|
||||
|
||||
MUTEX m_list_lock;
|
||||
|
||||
|
||||
public:
|
||||
FOOTPRINT_LIST() : m_lib_table( 0 )
|
||||
{
|
||||
|
@ -205,6 +202,9 @@ public:
|
|||
{
|
||||
}
|
||||
|
||||
virtual void WriteCacheToFile( wxTextFile* aFile ) { };
|
||||
virtual void ReadCacheFromFile( wxTextFile* aFile ) { };
|
||||
|
||||
/**
|
||||
* @return the number of items stored in list
|
||||
*/
|
||||
|
|
|
@ -179,7 +179,6 @@ void FOOTPRINT_LIST_IMPL::StartWorkers( FP_LIB_TABLE* aTable, wxString const* aN
|
|||
{
|
||||
m_loader = aLoader;
|
||||
m_lib_table = aTable;
|
||||
m_library = aNickname;
|
||||
|
||||
// Clear data before reading files
|
||||
m_count_finished.store( 0 );
|
||||
|
@ -326,7 +325,6 @@ bool FOOTPRINT_LIST_IMPL::JoinWorkers()
|
|||
|
||||
FOOTPRINT_LIST_IMPL::FOOTPRINT_LIST_IMPL() :
|
||||
m_loader( nullptr ),
|
||||
m_library( nullptr ),
|
||||
m_count_finished( 0 ),
|
||||
m_list_timestamp( 0 ),
|
||||
m_progress_reporter( nullptr ),
|
||||
|
@ -339,3 +337,72 @@ FOOTPRINT_LIST_IMPL::~FOOTPRINT_LIST_IMPL()
|
|||
{
|
||||
StopWorkers();
|
||||
}
|
||||
|
||||
|
||||
void FOOTPRINT_LIST_IMPL::WriteCacheToFile( wxTextFile* aCacheFile )
|
||||
{
|
||||
if( aCacheFile->Exists() )
|
||||
{
|
||||
aCacheFile->Open();
|
||||
aCacheFile->Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
aCacheFile->Create();
|
||||
}
|
||||
|
||||
aCacheFile->AddLine( wxString::Format( "%lld", m_list_timestamp ) );
|
||||
|
||||
for( auto& fpinfo : m_list )
|
||||
{
|
||||
aCacheFile->AddLine( fpinfo->GetLibNickname() );
|
||||
aCacheFile->AddLine( fpinfo->GetName() );
|
||||
aCacheFile->AddLine( fpinfo->GetDescription() );
|
||||
aCacheFile->AddLine( fpinfo->GetKeywords() );
|
||||
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 )
|
||||
{
|
||||
try
|
||||
{
|
||||
m_list.clear();
|
||||
|
||||
if( aCacheFile->Exists() )
|
||||
aCacheFile->Open();
|
||||
else
|
||||
return;
|
||||
|
||||
aCacheFile->GetFirstLine().ToLongLong( &m_list_timestamp );
|
||||
|
||||
while( aCacheFile->GetCurrentLine() + 6 < aCacheFile->GetLineCount() )
|
||||
{
|
||||
wxString libNickname = aCacheFile->GetNextLine();
|
||||
wxString name = aCacheFile->GetNextLine();
|
||||
wxString description = aCacheFile->GetNextLine();
|
||||
wxString keywords = aCacheFile->GetNextLine();
|
||||
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 ) );
|
||||
}
|
||||
}
|
||||
catch( ... )
|
||||
{
|
||||
// whatever went wrong, invalidate the cache
|
||||
m_list_timestamp = 0;
|
||||
}
|
||||
|
||||
if( aCacheFile->IsOpened() )
|
||||
aCacheFile->Close();
|
||||
}
|
||||
|
|
|
@ -37,21 +37,42 @@ public:
|
|||
FOOTPRINT_INFO_IMPL( FOOTPRINT_LIST* aOwner, const wxString& aNickname,
|
||||
const wxString& aFootprintName )
|
||||
{
|
||||
m_owner = aOwner;
|
||||
m_loaded = false;
|
||||
m_nickname = aNickname;
|
||||
m_fpname = aFootprintName;
|
||||
m_num = 0;
|
||||
m_pad_count = 0;
|
||||
m_unique_pad_count = 0;
|
||||
|
||||
m_owner = aOwner;
|
||||
m_loaded = false;
|
||||
load();
|
||||
}
|
||||
|
||||
// A constructor for cached items
|
||||
FOOTPRINT_INFO_IMPL( const wxString& aNickname, const wxString& aFootprintName,
|
||||
const wxString& aDescription, const wxString& aKeywords,
|
||||
int aOrderNum, unsigned int aPadCount, unsigned int aUniquePadCount )
|
||||
{
|
||||
m_nickname = aNickname;
|
||||
m_fpname = aFootprintName;
|
||||
m_num = aOrderNum;
|
||||
m_pad_count = aPadCount;
|
||||
m_unique_pad_count = aUniquePadCount;
|
||||
m_doc = aDescription;
|
||||
m_keywords = aKeywords;
|
||||
|
||||
m_owner = nullptr;
|
||||
m_loaded = true;
|
||||
}
|
||||
|
||||
|
||||
// A dummy constructor for use as a target in a binary search
|
||||
FOOTPRINT_INFO_IMPL( const wxString& aFootprintName )
|
||||
{
|
||||
m_fpname = aFootprintName;
|
||||
|
||||
m_owner = nullptr;
|
||||
m_loaded = true;
|
||||
}
|
||||
|
||||
protected:
|
||||
|
@ -62,7 +83,6 @@ protected:
|
|||
class FOOTPRINT_LIST_IMPL : public FOOTPRINT_LIST
|
||||
{
|
||||
FOOTPRINT_ASYNC_LOADER* m_loader;
|
||||
const wxString* m_library;
|
||||
std::vector<std::thread> m_threads;
|
||||
SYNC_QUEUE<wxString> m_queue_in;
|
||||
SYNC_QUEUE<wxString> m_queue_out;
|
||||
|
@ -96,6 +116,9 @@ public:
|
|||
FOOTPRINT_LIST_IMPL();
|
||||
virtual ~FOOTPRINT_LIST_IMPL();
|
||||
|
||||
void WriteCacheToFile( wxTextFile* aFile ) override;
|
||||
void ReadCacheFromFile( wxTextFile* aFile ) override;
|
||||
|
||||
bool ReadFootprintFiles( FP_LIB_TABLE* aTable, const wxString* aNickname = nullptr,
|
||||
PROGRESS_REPORTER* aProgressReporter = nullptr ) override;
|
||||
};
|
||||
|
|
|
@ -109,7 +109,6 @@ static inline long parseInt( const wxString& aValue, double aScalar )
|
|||
class GPCB_FPL_CACHE_ITEM
|
||||
{
|
||||
wxFileName m_file_name; ///< The the full file name and path of the footprint to cache.
|
||||
bool m_writable; ///< Writability status of the footprint file.
|
||||
std::unique_ptr<MODULE> m_module;
|
||||
|
||||
public:
|
||||
|
@ -122,10 +121,9 @@ public:
|
|||
|
||||
|
||||
GPCB_FPL_CACHE_ITEM::GPCB_FPL_CACHE_ITEM( MODULE* aModule, const wxFileName& aFileName ) :
|
||||
m_file_name( aFileName ),
|
||||
m_module( aModule )
|
||||
{
|
||||
m_file_name = aFileName;
|
||||
m_writable = true; // temporary init
|
||||
}
|
||||
|
||||
|
||||
|
@ -200,27 +198,13 @@ public:
|
|||
* parent directory).
|
||||
* Timestamps should not be considered ordered. They either match or they don't.
|
||||
*/
|
||||
long long GetTimestamp();
|
||||
static long long GetTimestamp( const wxString& aLibPath );
|
||||
|
||||
/**
|
||||
* Function IsModified
|
||||
* Return true if the cache is not up-to-date.
|
||||
*/
|
||||
bool IsModified();
|
||||
|
||||
/**
|
||||
* Function IsPath
|
||||
* checks if \a aPath is the same as the current cache path.
|
||||
*
|
||||
* This tests paths by converting \a aPath using the native separators. Internally
|
||||
* #FP_CACHE stores the current path using native separators. This prevents path
|
||||
* miscompares on Windows due to the fact that paths can be stored with / instead of \\
|
||||
* in the footprint library table.
|
||||
*
|
||||
* @param aPath is the library path to test against.
|
||||
* @return true if \a aPath is the same as the cache path.
|
||||
*/
|
||||
bool IsPath( const wxString& aPath ) const;
|
||||
};
|
||||
|
||||
|
||||
|
@ -235,36 +219,35 @@ GPCB_FPL_CACHE::GPCB_FPL_CACHE( GPCB_PLUGIN* aOwner, const wxString& aLibraryPat
|
|||
|
||||
void GPCB_FPL_CACHE::Load()
|
||||
{
|
||||
m_cache_dirty = false;
|
||||
m_cache_timestamp = 0;
|
||||
|
||||
// Note: like our .pretty footprint libraries, the gpcb footprint libraries are folders,
|
||||
// and the footprints are the .fp files inside this folder.
|
||||
|
||||
wxDir dir( m_lib_path.GetPath() );
|
||||
WX_DIR dir( m_lib_path.GetPath() );
|
||||
|
||||
if( !dir.IsOpened() )
|
||||
{
|
||||
m_cache_timestamp = 0;
|
||||
m_cache_dirty = false;
|
||||
|
||||
THROW_IO_ERROR( wxString::Format( _( "footprint library path \"%s\" does not exist" ),
|
||||
m_lib_path.GetPath().GetData() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cache_timestamp = m_lib_path.GetModificationTime().GetValue().GetValue();
|
||||
m_cache_dirty = false;
|
||||
}
|
||||
|
||||
wxString fpFileName;
|
||||
wxString wildcard = wxT( "*." ) + GedaPcbFootprintLibFileExtension;
|
||||
|
||||
if( !dir.GetFirst( &fpFileName, wildcard, wxDIR_FILES ) )
|
||||
// wxFileName construction is egregiously slow. Construct it once and just swap out
|
||||
// the filename.
|
||||
WX_FILENAME fn( m_lib_path.GetPath(), wxT( "dummy." ) + GedaPcbFootprintLibFileExtension );
|
||||
|
||||
if( !dir.GetFirst( &fpFileName, wildcard ) )
|
||||
return;
|
||||
|
||||
wxString cacheErrorMsg;
|
||||
|
||||
do
|
||||
{
|
||||
wxFileName fn( m_lib_path.GetPath(), fpFileName );
|
||||
fn.SetFullName( fpFileName );
|
||||
|
||||
// Queue I/O errors so only files that fail to parse don't get loaded.
|
||||
try
|
||||
|
@ -313,50 +296,39 @@ void GPCB_FPL_CACHE::Remove( const wxString& aFootprintName )
|
|||
}
|
||||
|
||||
|
||||
bool GPCB_FPL_CACHE::IsPath( const wxString& aPath ) const
|
||||
{
|
||||
// Converts path separators to native path separators
|
||||
wxFileName newPath;
|
||||
newPath.AssignDir( aPath );
|
||||
|
||||
return m_lib_path == newPath;
|
||||
}
|
||||
|
||||
|
||||
bool GPCB_FPL_CACHE::IsModified()
|
||||
{
|
||||
if( m_cache_dirty )
|
||||
return true;
|
||||
else
|
||||
return GetTimestamp() != m_cache_timestamp;
|
||||
m_cache_dirty = m_cache_dirty || GetTimestamp( m_lib_path.GetFullPath() ) != m_cache_timestamp;
|
||||
|
||||
return m_cache_dirty;
|
||||
}
|
||||
|
||||
|
||||
long long GPCB_FPL_CACHE::GetTimestamp()
|
||||
long long GPCB_FPL_CACHE::GetTimestamp( const wxString& aLibPath )
|
||||
{
|
||||
// Avoid expensive GetModificationTime checks if we already know we're dirty
|
||||
if( m_cache_dirty )
|
||||
return wxDateTime::Now().GetValue().GetValue();
|
||||
|
||||
long long files_timestamp = 0;
|
||||
|
||||
if( m_lib_path.DirExists() )
|
||||
{
|
||||
files_timestamp = m_lib_path.GetModificationTime().GetValue().GetValue();
|
||||
// wxFileName construction is egregiously slow. Construct it once and just swap out
|
||||
// the filename.
|
||||
WX_FILENAME fn( aLibPath, wxT( "dummy." ) + GedaPcbFootprintLibFileExtension );
|
||||
|
||||
for( MODULE_CITER it = m_modules.begin(); it != m_modules.end(); ++it )
|
||||
WX_DIR dir( aLibPath );
|
||||
|
||||
if( dir.IsOpened() )
|
||||
{
|
||||
wxString fpFileName;
|
||||
wxString wildcard = wxT( "*." ) + GedaPcbFootprintLibFileExtension;
|
||||
|
||||
if( dir.GetFirst( &fpFileName, wildcard ) )
|
||||
{
|
||||
wxFileName moduleFile = it->second->GetFileName();
|
||||
if( moduleFile.FileExists() )
|
||||
files_timestamp += moduleFile.GetModificationTime().GetValue().GetValue();
|
||||
do
|
||||
{
|
||||
fn.SetFullName( fpFileName );
|
||||
files_timestamp += fn.GetTimestamp();
|
||||
} while( dir.GetNext( &fpFileName ) );
|
||||
}
|
||||
}
|
||||
|
||||
// If the new timestamp doesn't match the cache timestamp, then save ourselves the
|
||||
// expensive calls next time
|
||||
if( m_cache_timestamp != files_timestamp )
|
||||
m_cache_dirty = true;
|
||||
|
||||
return files_timestamp;
|
||||
}
|
||||
|
||||
|
@ -1092,11 +1064,7 @@ bool GPCB_PLUGIN::FootprintLibDelete( const wxString& aLibraryPath, const PROPER
|
|||
|
||||
long long GPCB_PLUGIN::GetLibraryTimestamp( const wxString& aLibraryPath ) const
|
||||
{
|
||||
// If we have no cache, return a number which won't match any stored timestamps
|
||||
if( !m_cache || !m_cache->IsPath( aLibraryPath ) )
|
||||
return wxDateTime::Now().GetValue().GetValue();
|
||||
|
||||
return m_cache->GetTimestamp();
|
||||
return GPCB_FPL_CACHE::GetTimestamp( aLibraryPath );
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -152,7 +152,7 @@ public:
|
|||
* parent directory).
|
||||
* Timestamps should not be considered ordered. They either match or they don't.
|
||||
*/
|
||||
long long GetTimestamp();
|
||||
static long long GetTimestamp( const wxString& aLibPath );
|
||||
|
||||
/**
|
||||
* Function IsModified
|
||||
|
@ -257,51 +257,49 @@ void FP_CACHE::Save( MODULE* aModule )
|
|||
|
||||
void FP_CACHE::Load()
|
||||
{
|
||||
wxDir dir( m_lib_raw_path );
|
||||
m_cache_dirty = false;
|
||||
m_cache_timestamp = 0;
|
||||
|
||||
WX_DIR dir( m_lib_raw_path );
|
||||
|
||||
if( !dir.IsOpened() )
|
||||
{
|
||||
m_cache_timestamp = 0;
|
||||
m_cache_dirty = false;
|
||||
|
||||
wxString msg = wxString::Format( _( "Footprint library path \"%s\" does not exist" ),
|
||||
m_lib_raw_path );
|
||||
THROW_IO_ERROR( msg );
|
||||
}
|
||||
else
|
||||
{
|
||||
m_cache_timestamp = m_lib_path.GetModificationTime().GetValue().GetValue();
|
||||
m_cache_dirty = false;
|
||||
}
|
||||
|
||||
wxString fpFileName;
|
||||
wxString wildcard = wxT( "*." ) + KiCadFootprintFileExtension;
|
||||
|
||||
if( dir.GetFirst( &fpFileName, wildcard, wxDIR_FILES ) )
|
||||
// wxFileName construction is egregiously slow. Construct it once and just swap out
|
||||
// the filename.
|
||||
WX_FILENAME fn( m_lib_raw_path, wxT( "dummy." ) + KiCadFootprintFileExtension );
|
||||
|
||||
if( dir.GetFirst( &fpFileName, wildcard ) )
|
||||
{
|
||||
wxString cacheError;
|
||||
|
||||
do
|
||||
{
|
||||
// prepend the libpath into fullPath
|
||||
wxFileName fullPath( m_lib_raw_path, fpFileName );
|
||||
fn.SetFullName( fpFileName );
|
||||
|
||||
// Queue I/O errors so only files that fail to parse don't get loaded.
|
||||
try
|
||||
{
|
||||
FILE_LINE_READER reader( fullPath.GetFullPath() );
|
||||
FILE_LINE_READER reader( fn.GetFullPath() );
|
||||
|
||||
m_owner->m_parser->SetLineReader( &reader );
|
||||
|
||||
MODULE* footprint = (MODULE*) m_owner->m_parser->Parse();
|
||||
|
||||
// The footprint name is the file name without the extension.
|
||||
wxString fpName = fullPath.GetName();
|
||||
wxString fpName = fn.GetName();
|
||||
|
||||
footprint->SetFPID( LIB_ID( wxEmptyString, fpName ) );
|
||||
m_modules.insert( fpName, new FP_CACHE_ITEM( footprint, fullPath ) );
|
||||
m_modules.insert( fpName, new FP_CACHE_ITEM( footprint, fn ) );
|
||||
|
||||
m_cache_timestamp += fullPath.GetModificationTime().GetValue().GetValue();
|
||||
m_cache_timestamp += fn.GetTimestamp();
|
||||
}
|
||||
catch( const IO_ERROR& ioe )
|
||||
{
|
||||
|
@ -345,64 +343,37 @@ bool FP_CACHE::IsPath( const wxString& aPath ) const
|
|||
|
||||
bool FP_CACHE::IsModified()
|
||||
{
|
||||
if( m_cache_dirty )
|
||||
return true;
|
||||
else
|
||||
return GetTimestamp() != m_cache_timestamp;
|
||||
m_cache_dirty = m_cache_dirty || GetTimestamp( m_lib_path.GetFullPath() ) != m_cache_timestamp;
|
||||
|
||||
return m_cache_dirty;
|
||||
}
|
||||
|
||||
|
||||
long long FP_CACHE::GetTimestamp()
|
||||
long long FP_CACHE::GetTimestamp( const wxString& aLibPath )
|
||||
{
|
||||
// Avoid expensive GetModificationTime checks if we already know we're dirty
|
||||
if( m_cache_dirty )
|
||||
return wxDateTime::Now().GetValue().GetValue();
|
||||
|
||||
long long files_timestamp = 0;
|
||||
|
||||
if( m_lib_path.DirExists() )
|
||||
// wxFileName construction is egregiously slow. Construct it once and just
|
||||
// swap out the filename for each file.
|
||||
WX_FILENAME fn( aLibPath, wxT( "dummy." ) + KiCadFootprintFileExtension );
|
||||
|
||||
WX_DIR dir( aLibPath );
|
||||
|
||||
if( dir.IsOpened() )
|
||||
{
|
||||
files_timestamp = m_lib_path.GetModificationTime().GetValue().GetValue();
|
||||
wxString fpFileName;
|
||||
wxString wildcard = wxT( "*." ) + KiCadFootprintFileExtension;
|
||||
|
||||
for( MODULE_CITER it = m_modules.begin(); it != m_modules.end(); ++it )
|
||||
if( dir.GetFirst( &fpFileName, wildcard ) )
|
||||
{
|
||||
const wxFileName& fn = it->second->GetFileName();
|
||||
#ifdef __WINDOWS__
|
||||
if( fn.FileExists() )
|
||||
files_timestamp += fn.GetModificationTime().GetValue().GetValue();
|
||||
#else
|
||||
// By stat-ing the file ourselves we save wxWidgets from doing it three times:
|
||||
// fn.Exists( wxFILE_EXISTS_SYMLINK ), fn.FileExists() & fn.GetModificationTime()
|
||||
struct stat fn_stat;
|
||||
wxLstat( fn.GetFullPath(), &fn_stat );
|
||||
|
||||
// Timestamp the source file, not the symlink
|
||||
if( S_ISLNK( fn_stat.st_mode ) ) // wxFILE_EXISTS_SYMLINK
|
||||
do
|
||||
{
|
||||
char buffer[ PATH_MAX + 1 ];
|
||||
ssize_t pathLen = readlink( TO_UTF8( fn.GetFullPath() ), buffer, PATH_MAX );
|
||||
|
||||
if( pathLen > 0 )
|
||||
{
|
||||
buffer[ pathLen ] = '\0';
|
||||
wxFileName srcFn;
|
||||
srcFn.Assign( fn.GetPath() + wxT( "/" ) + wxString::FromUTF8( buffer ) );
|
||||
srcFn.Normalize();
|
||||
wxLstat( srcFn.GetFullPath(), &fn_stat );
|
||||
}
|
||||
}
|
||||
|
||||
if( S_ISREG( fn_stat.st_mode ) ) // wxFileExists()
|
||||
files_timestamp += fn_stat.st_mtime * 1000;
|
||||
#endif
|
||||
fn.SetFullName( fpFileName );
|
||||
files_timestamp += fn.GetTimestamp();
|
||||
} while( dir.GetNext( &fpFileName ) );
|
||||
}
|
||||
}
|
||||
|
||||
// If the new timestamp doesn't match the cache timestamp, then save ourselves the
|
||||
// expensive calls next time
|
||||
if( m_cache_timestamp != files_timestamp )
|
||||
m_cache_dirty = true;
|
||||
|
||||
return files_timestamp;
|
||||
}
|
||||
|
||||
|
@ -2200,11 +2171,7 @@ void PCB_IO::FootprintDelete( const wxString& aLibraryPath, const wxString& aFoo
|
|||
|
||||
long long PCB_IO::GetLibraryTimestamp( const wxString& aLibraryPath ) const
|
||||
{
|
||||
// If we have no cache, return a number which won't match any stored timestamps
|
||||
if( !m_cache || !m_cache->IsPath( aLibraryPath ) )
|
||||
return wxDateTime::Now().GetValue().GetValue();
|
||||
|
||||
return m_cache->GetTimestamp();
|
||||
return FP_CACHE::GetTimestamp( aLibraryPath );
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3164,30 +3164,20 @@ struct LP_CACHE
|
|||
{
|
||||
LEGACY_PLUGIN* m_owner; // my owner, I need its LEGACY_PLUGIN::loadMODULE()
|
||||
wxString m_lib_path;
|
||||
wxDateTime m_mod_time;
|
||||
MODULE_MAP m_modules; // map or tuple of footprint_name vs. MODULE*
|
||||
bool m_writable;
|
||||
|
||||
bool m_cache_dirty; // Stored separately because it's expensive to check
|
||||
// m_cache_timestamp against all the files.
|
||||
long long m_cache_timestamp; // A hash of the timestamps for all the footprint
|
||||
// files.
|
||||
|
||||
LP_CACHE( LEGACY_PLUGIN* aOwner, const wxString& aLibraryPath );
|
||||
|
||||
// Most all functions in this class throw IO_ERROR exceptions. There are no
|
||||
// error codes nor user interface calls from here, nor in any PLUGIN.
|
||||
// Catch these exceptions higher up please.
|
||||
|
||||
/// save the entire legacy library to m_lib_path;
|
||||
void Save();
|
||||
|
||||
void SaveHeader( FILE* aFile );
|
||||
|
||||
void SaveIndex( FILE* aFile );
|
||||
|
||||
void SaveModules( FILE* aFile );
|
||||
|
||||
void SaveEndOfFile( FILE* aFile )
|
||||
{
|
||||
fprintf( aFile, "$EndLIBRARY\n" );
|
||||
}
|
||||
|
||||
void Load();
|
||||
|
||||
void ReadAndVerifyHeader( LINE_READER* aReader );
|
||||
|
@ -3196,32 +3186,39 @@ struct LP_CACHE
|
|||
|
||||
void LoadModules( LINE_READER* aReader );
|
||||
|
||||
wxDateTime GetLibModificationTime();
|
||||
bool IsModified();
|
||||
static long long GetTimestamp( const wxString& aLibPath );
|
||||
};
|
||||
|
||||
|
||||
LP_CACHE::LP_CACHE( LEGACY_PLUGIN* aOwner, const wxString& aLibraryPath ) :
|
||||
m_owner( aOwner ),
|
||||
m_lib_path( aLibraryPath ),
|
||||
m_writable( true )
|
||||
m_writable( true ),
|
||||
m_cache_timestamp( 0 ),
|
||||
m_cache_dirty( true )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
wxDateTime LP_CACHE::GetLibModificationTime()
|
||||
bool LP_CACHE::IsModified()
|
||||
{
|
||||
wxFileName fn( m_lib_path );
|
||||
m_cache_dirty = m_cache_dirty || GetTimestamp( m_lib_path ) != m_cache_timestamp;
|
||||
|
||||
// update the writable flag while we have a wxFileName, in a network this
|
||||
// is possibly quite dynamic anyway.
|
||||
m_writable = fn.IsFileWritable();
|
||||
return m_cache_dirty;
|
||||
}
|
||||
|
||||
return fn.GetModificationTime();
|
||||
|
||||
long long LP_CACHE::GetTimestamp( const wxString& aLibPath )
|
||||
{
|
||||
return wxFileName( aLibPath ).GetModificationTime().GetValue().GetValue();
|
||||
}
|
||||
|
||||
|
||||
void LP_CACHE::Load()
|
||||
{
|
||||
m_cache_dirty = false;
|
||||
|
||||
FILE_LINE_READER reader( m_lib_path );
|
||||
|
||||
ReadAndVerifyHeader( &reader );
|
||||
|
@ -3231,7 +3228,7 @@ void LP_CACHE::Load()
|
|||
// Remember the file modification time of library file when the
|
||||
// cache snapshot was made, so that in a networked environment we will
|
||||
// reload the cache as needed.
|
||||
m_mod_time = GetLibModificationTime();
|
||||
m_cache_timestamp = GetTimestamp( m_lib_path );
|
||||
}
|
||||
|
||||
|
||||
|
@ -3388,19 +3385,13 @@ void LP_CACHE::LoadModules( LINE_READER* aReader )
|
|||
|
||||
long long LEGACY_PLUGIN::GetLibraryTimestamp( const wxString& aLibraryPath ) const
|
||||
{
|
||||
// If we have no cache, return a number which won't match any stored timestamps
|
||||
if( !m_cache || m_cache->m_lib_path != aLibraryPath )
|
||||
return wxDateTime::Now().GetValue().GetValue();
|
||||
|
||||
return m_cache->GetLibModificationTime().GetValue().GetValue();
|
||||
return LP_CACHE::GetTimestamp( aLibraryPath );
|
||||
}
|
||||
|
||||
|
||||
void LEGACY_PLUGIN::cacheLib( const wxString& aLibraryPath )
|
||||
{
|
||||
if( !m_cache || m_cache->m_lib_path != aLibraryPath ||
|
||||
// somebody else on a network touched the library:
|
||||
m_cache->m_mod_time != m_cache->GetLibModificationTime() )
|
||||
if( !m_cache || m_cache->m_lib_path != aLibraryPath || m_cache->IsModified() )
|
||||
{
|
||||
// a spectacular episode in memory management:
|
||||
delete m_cache;
|
||||
|
|
|
@ -192,7 +192,7 @@ MODULE* PCB_BASE_FRAME::SelectFootprintFromLibTree( bool aAllowBrowser )
|
|||
|
||||
static wxString lastComponentName;
|
||||
|
||||
WX_PROGRESS_REPORTER progressReporter( this, _( "Loading Footprint Libraries" ), 2 );
|
||||
WX_PROGRESS_REPORTER progressReporter( this, _( "Loading Footprint Libraries" ), 3 );
|
||||
GFootprintList.ReadFootprintFiles( fpTable, nullptr, &progressReporter );
|
||||
progressReporter.Show( false );
|
||||
|
||||
|
|
|
@ -28,6 +28,31 @@
|
|||
#include <gal/graphics_abstraction_layer.h>
|
||||
#include <class_board.h>
|
||||
#include <view/view.h>
|
||||
#include "footprint_info_impl.h"
|
||||
#include <project.h>
|
||||
|
||||
PCB_BASE_EDIT_FRAME::PCB_BASE_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent,
|
||||
FRAME_T aFrameType, const wxString& aTitle,
|
||||
const wxPoint& aPos, const wxSize& aSize, long aStyle,
|
||||
const wxString& aFrameName ) :
|
||||
PCB_BASE_FRAME( aKiway, aParent, aFrameType, aTitle, aPos, aSize, aStyle, aFrameName ),
|
||||
m_rotationAngle( 900 ), m_undoRedoBlocked( false )
|
||||
{
|
||||
static bool oneShot = true;
|
||||
|
||||
if( oneShot )
|
||||
{
|
||||
wxTextFile footprintInfoCache( Prj().GetProjectPath() + "fp-info-cache" );
|
||||
GFootprintList.ReadCacheFromFile( &footprintInfoCache );
|
||||
oneShot = false;
|
||||
}
|
||||
}
|
||||
|
||||
PCB_BASE_EDIT_FRAME::~PCB_BASE_EDIT_FRAME()
|
||||
{
|
||||
wxTextFile footprintInfoCache( Prj().GetProjectPath() + "fp-info-cache" );
|
||||
GFootprintList.WriteCacheToFile( &footprintInfoCache );
|
||||
}
|
||||
|
||||
|
||||
void PCB_BASE_EDIT_FRAME::SetRotationAngle( int aRotationAngle )
|
||||
|
|
|
@ -37,12 +37,9 @@ class PCB_BASE_EDIT_FRAME : public PCB_BASE_FRAME
|
|||
public:
|
||||
PCB_BASE_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent, FRAME_T aFrameType,
|
||||
const wxString& aTitle, const wxPoint& aPos, const wxSize& aSize,
|
||||
long aStyle, const wxString& aFrameName ) :
|
||||
PCB_BASE_FRAME( aKiway, aParent, aFrameType, aTitle, aPos, aSize, aStyle, aFrameName ),
|
||||
m_rotationAngle( 900 ), m_undoRedoBlocked( false )
|
||||
{}
|
||||
long aStyle, const wxString& aFrameName );
|
||||
|
||||
virtual ~PCB_BASE_EDIT_FRAME() {};
|
||||
virtual ~PCB_BASE_EDIT_FRAME();
|
||||
|
||||
/**
|
||||
* Function GetModel()
|
||||
|
|
Loading…
Reference in New Issue