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:
Jeff Young 2018-08-03 17:53:38 +01:00
parent 5e8e2570ba
commit f3f814e622
11 changed files with 400 additions and 179 deletions

View File

@ -529,3 +529,123 @@ bool std::less<wxPoint>::operator()( const wxPoint& aA, const wxPoint& aB ) cons
return aA.x < aB.x; return aA.x < aB.x;
} }
#endif #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;
}

View File

@ -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_ #endif // INCLUDE__COMMON_H_

View File

@ -145,7 +145,7 @@ protected:
} }
/// lazily load stuff not filled in by constructor. This may throw IO_ERRORS. /// 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 FOOTPRINT_LIST* m_owner; ///< provides access to FP_LIB_TABLE
@ -193,9 +193,6 @@ protected:
FPILIST m_list; FPILIST m_list;
ERRLIST m_errors; ///< some can be PARSE_ERRORs also ERRLIST m_errors; ///< some can be PARSE_ERRORs also
MUTEX m_list_lock;
public: public:
FOOTPRINT_LIST() : m_lib_table( 0 ) 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 * @return the number of items stored in list
*/ */

View File

@ -179,7 +179,6 @@ void FOOTPRINT_LIST_IMPL::StartWorkers( FP_LIB_TABLE* aTable, wxString const* aN
{ {
m_loader = aLoader; m_loader = aLoader;
m_lib_table = aTable; m_lib_table = aTable;
m_library = aNickname;
// Clear data before reading files // Clear data before reading files
m_count_finished.store( 0 ); m_count_finished.store( 0 );
@ -326,7 +325,6 @@ bool FOOTPRINT_LIST_IMPL::JoinWorkers()
FOOTPRINT_LIST_IMPL::FOOTPRINT_LIST_IMPL() : FOOTPRINT_LIST_IMPL::FOOTPRINT_LIST_IMPL() :
m_loader( nullptr ), m_loader( nullptr ),
m_library( nullptr ),
m_count_finished( 0 ), m_count_finished( 0 ),
m_list_timestamp( 0 ), m_list_timestamp( 0 ),
m_progress_reporter( nullptr ), m_progress_reporter( nullptr ),
@ -339,3 +337,72 @@ FOOTPRINT_LIST_IMPL::~FOOTPRINT_LIST_IMPL()
{ {
StopWorkers(); 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();
}

View File

@ -37,21 +37,42 @@ public:
FOOTPRINT_INFO_IMPL( FOOTPRINT_LIST* aOwner, const wxString& aNickname, FOOTPRINT_INFO_IMPL( FOOTPRINT_LIST* aOwner, const wxString& aNickname,
const wxString& aFootprintName ) const wxString& aFootprintName )
{ {
m_owner = aOwner;
m_loaded = false;
m_nickname = aNickname; m_nickname = aNickname;
m_fpname = aFootprintName; m_fpname = aFootprintName;
m_num = 0; m_num = 0;
m_pad_count = 0; m_pad_count = 0;
m_unique_pad_count = 0; m_unique_pad_count = 0;
m_owner = aOwner;
m_loaded = false;
load(); 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 // A dummy constructor for use as a target in a binary search
FOOTPRINT_INFO_IMPL( const wxString& aFootprintName ) FOOTPRINT_INFO_IMPL( const wxString& aFootprintName )
{ {
m_fpname = aFootprintName; m_fpname = aFootprintName;
m_owner = nullptr;
m_loaded = true;
} }
protected: protected:
@ -62,7 +83,6 @@ protected:
class FOOTPRINT_LIST_IMPL : public FOOTPRINT_LIST class FOOTPRINT_LIST_IMPL : public FOOTPRINT_LIST
{ {
FOOTPRINT_ASYNC_LOADER* m_loader; FOOTPRINT_ASYNC_LOADER* m_loader;
const wxString* m_library;
std::vector<std::thread> m_threads; std::vector<std::thread> m_threads;
SYNC_QUEUE<wxString> m_queue_in; SYNC_QUEUE<wxString> m_queue_in;
SYNC_QUEUE<wxString> m_queue_out; SYNC_QUEUE<wxString> m_queue_out;
@ -96,6 +116,9 @@ public:
FOOTPRINT_LIST_IMPL(); FOOTPRINT_LIST_IMPL();
virtual ~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, bool ReadFootprintFiles( FP_LIB_TABLE* aTable, const wxString* aNickname = nullptr,
PROGRESS_REPORTER* aProgressReporter = nullptr ) override; PROGRESS_REPORTER* aProgressReporter = nullptr ) override;
}; };

View File

@ -109,7 +109,6 @@ static inline long parseInt( const wxString& aValue, double aScalar )
class GPCB_FPL_CACHE_ITEM class GPCB_FPL_CACHE_ITEM
{ {
wxFileName m_file_name; ///< The the full file name and path of the footprint to cache. 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; std::unique_ptr<MODULE> m_module;
public: public:
@ -122,10 +121,9 @@ public:
GPCB_FPL_CACHE_ITEM::GPCB_FPL_CACHE_ITEM( MODULE* aModule, const wxFileName& aFileName ) : GPCB_FPL_CACHE_ITEM::GPCB_FPL_CACHE_ITEM( MODULE* aModule, const wxFileName& aFileName ) :
m_file_name( aFileName ),
m_module( aModule ) m_module( aModule )
{ {
m_file_name = aFileName;
m_writable = true; // temporary init
} }
@ -200,27 +198,13 @@ public:
* parent directory). * parent directory).
* Timestamps should not be considered ordered. They either match or they don't. * 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 * Function IsModified
* Return true if the cache is not up-to-date. * Return true if the cache is not up-to-date.
*/ */
bool IsModified(); 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() 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, // Note: like our .pretty footprint libraries, the gpcb footprint libraries are folders,
// and the footprints are the .fp files inside this folder. // 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() ) if( !dir.IsOpened() )
{ {
m_cache_timestamp = 0;
m_cache_dirty = false;
THROW_IO_ERROR( wxString::Format( _( "footprint library path \"%s\" does not exist" ), THROW_IO_ERROR( wxString::Format( _( "footprint library path \"%s\" does not exist" ),
m_lib_path.GetPath().GetData() ) ); m_lib_path.GetPath().GetData() ) );
} }
else
{
m_cache_timestamp = m_lib_path.GetModificationTime().GetValue().GetValue();
m_cache_dirty = false;
}
wxString fpFileName; wxString fpFileName;
wxString wildcard = wxT( "*." ) + GedaPcbFootprintLibFileExtension; 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; return;
wxString cacheErrorMsg; wxString cacheErrorMsg;
do 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. // Queue I/O errors so only files that fail to parse don't get loaded.
try 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() bool GPCB_FPL_CACHE::IsModified()
{ {
if( m_cache_dirty ) m_cache_dirty = m_cache_dirty || GetTimestamp( m_lib_path.GetFullPath() ) != m_cache_timestamp;
return true;
else return m_cache_dirty;
return GetTimestamp() != m_cache_timestamp;
} }
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; long long files_timestamp = 0;
if( m_lib_path.DirExists() ) // wxFileName construction is egregiously slow. Construct it once and just swap out
{ // the filename.
files_timestamp = m_lib_path.GetModificationTime().GetValue().GetValue(); 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() )
{ {
wxFileName moduleFile = it->second->GetFileName(); wxString fpFileName;
if( moduleFile.FileExists() ) wxString wildcard = wxT( "*." ) + GedaPcbFootprintLibFileExtension;
files_timestamp += moduleFile.GetModificationTime().GetValue().GetValue();
if( dir.GetFirst( &fpFileName, wildcard ) )
{
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; 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 long long GPCB_PLUGIN::GetLibraryTimestamp( const wxString& aLibraryPath ) const
{ {
// If we have no cache, return a number which won't match any stored timestamps return GPCB_FPL_CACHE::GetTimestamp( aLibraryPath );
if( !m_cache || !m_cache->IsPath( aLibraryPath ) )
return wxDateTime::Now().GetValue().GetValue();
return m_cache->GetTimestamp();
} }

View File

@ -152,7 +152,7 @@ public:
* parent directory). * parent directory).
* Timestamps should not be considered ordered. They either match or they don't. * 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 * Function IsModified
@ -257,51 +257,49 @@ void FP_CACHE::Save( MODULE* aModule )
void FP_CACHE::Load() 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() ) if( !dir.IsOpened() )
{ {
m_cache_timestamp = 0;
m_cache_dirty = false;
wxString msg = wxString::Format( _( "Footprint library path \"%s\" does not exist" ), wxString msg = wxString::Format( _( "Footprint library path \"%s\" does not exist" ),
m_lib_raw_path ); m_lib_raw_path );
THROW_IO_ERROR( msg ); THROW_IO_ERROR( msg );
} }
else
{
m_cache_timestamp = m_lib_path.GetModificationTime().GetValue().GetValue();
m_cache_dirty = false;
}
wxString fpFileName; wxString fpFileName;
wxString wildcard = wxT( "*." ) + KiCadFootprintFileExtension; 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; wxString cacheError;
do do
{ {
// prepend the libpath into fullPath fn.SetFullName( fpFileName );
wxFileName fullPath( m_lib_raw_path, fpFileName );
// Queue I/O errors so only files that fail to parse don't get loaded. // Queue I/O errors so only files that fail to parse don't get loaded.
try try
{ {
FILE_LINE_READER reader( fullPath.GetFullPath() ); FILE_LINE_READER reader( fn.GetFullPath() );
m_owner->m_parser->SetLineReader( &reader ); m_owner->m_parser->SetLineReader( &reader );
MODULE* footprint = (MODULE*) m_owner->m_parser->Parse(); MODULE* footprint = (MODULE*) m_owner->m_parser->Parse();
// The footprint name is the file name without the extension. // The footprint name is the file name without the extension.
wxString fpName = fullPath.GetName(); wxString fpName = fn.GetName();
footprint->SetFPID( LIB_ID( wxEmptyString, fpName ) ); 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 ) catch( const IO_ERROR& ioe )
{ {
@ -345,64 +343,37 @@ bool FP_CACHE::IsPath( const wxString& aPath ) const
bool FP_CACHE::IsModified() bool FP_CACHE::IsModified()
{ {
if( m_cache_dirty ) m_cache_dirty = m_cache_dirty || GetTimestamp( m_lib_path.GetFullPath() ) != m_cache_timestamp;
return true;
else return m_cache_dirty;
return GetTimestamp() != m_cache_timestamp;
} }
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; 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.
files_timestamp = m_lib_path.GetModificationTime().GetValue().GetValue(); WX_FILENAME fn( aLibPath, wxT( "dummy." ) + KiCadFootprintFileExtension );
for( MODULE_CITER it = m_modules.begin(); it != m_modules.end(); ++it ) WX_DIR dir( aLibPath );
{
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( dir.IsOpened() )
if( S_ISLNK( fn_stat.st_mode ) ) // wxFILE_EXISTS_SYMLINK
{ {
char buffer[ PATH_MAX + 1 ]; wxString fpFileName;
ssize_t pathLen = readlink( TO_UTF8( fn.GetFullPath() ), buffer, PATH_MAX ); wxString wildcard = wxT( "*." ) + KiCadFootprintFileExtension;
if( pathLen > 0 ) if( dir.GetFirst( &fpFileName, wildcard ) )
{ {
buffer[ pathLen ] = '\0'; do
wxFileName srcFn; {
srcFn.Assign( fn.GetPath() + wxT( "/" ) + wxString::FromUTF8( buffer ) ); fn.SetFullName( fpFileName );
srcFn.Normalize(); files_timestamp += fn.GetTimestamp();
wxLstat( srcFn.GetFullPath(), &fn_stat ); } while( dir.GetNext( &fpFileName ) );
} }
} }
if( S_ISREG( fn_stat.st_mode ) ) // wxFileExists()
files_timestamp += fn_stat.st_mtime * 1000;
#endif
}
}
// 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; 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 long long PCB_IO::GetLibraryTimestamp( const wxString& aLibraryPath ) const
{ {
// If we have no cache, return a number which won't match any stored timestamps return FP_CACHE::GetTimestamp( aLibraryPath );
if( !m_cache || !m_cache->IsPath( aLibraryPath ) )
return wxDateTime::Now().GetValue().GetValue();
return m_cache->GetTimestamp();
} }

View File

@ -3164,30 +3164,20 @@ struct LP_CACHE
{ {
LEGACY_PLUGIN* m_owner; // my owner, I need its LEGACY_PLUGIN::loadMODULE() LEGACY_PLUGIN* m_owner; // my owner, I need its LEGACY_PLUGIN::loadMODULE()
wxString m_lib_path; wxString m_lib_path;
wxDateTime m_mod_time;
MODULE_MAP m_modules; // map or tuple of footprint_name vs. MODULE* MODULE_MAP m_modules; // map or tuple of footprint_name vs. MODULE*
bool m_writable; 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 ); LP_CACHE( LEGACY_PLUGIN* aOwner, const wxString& aLibraryPath );
// Most all functions in this class throw IO_ERROR exceptions. There are no // 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. // error codes nor user interface calls from here, nor in any PLUGIN.
// Catch these exceptions higher up please. // 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 Load();
void ReadAndVerifyHeader( LINE_READER* aReader ); void ReadAndVerifyHeader( LINE_READER* aReader );
@ -3196,32 +3186,39 @@ struct LP_CACHE
void LoadModules( LINE_READER* aReader ); 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 ) : LP_CACHE::LP_CACHE( LEGACY_PLUGIN* aOwner, const wxString& aLibraryPath ) :
m_owner( aOwner ), m_owner( aOwner ),
m_lib_path( aLibraryPath ), 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 return m_cache_dirty;
// is possibly quite dynamic anyway. }
m_writable = fn.IsFileWritable();
return fn.GetModificationTime();
long long LP_CACHE::GetTimestamp( const wxString& aLibPath )
{
return wxFileName( aLibPath ).GetModificationTime().GetValue().GetValue();
} }
void LP_CACHE::Load() void LP_CACHE::Load()
{ {
m_cache_dirty = false;
FILE_LINE_READER reader( m_lib_path ); FILE_LINE_READER reader( m_lib_path );
ReadAndVerifyHeader( &reader ); ReadAndVerifyHeader( &reader );
@ -3231,7 +3228,7 @@ void LP_CACHE::Load()
// Remember the file modification time of library file when the // Remember the file modification time of library file when the
// cache snapshot was made, so that in a networked environment we will // cache snapshot was made, so that in a networked environment we will
// reload the cache as needed. // 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 long long LEGACY_PLUGIN::GetLibraryTimestamp( const wxString& aLibraryPath ) const
{ {
// If we have no cache, return a number which won't match any stored timestamps return LP_CACHE::GetTimestamp( aLibraryPath );
if( !m_cache || m_cache->m_lib_path != aLibraryPath )
return wxDateTime::Now().GetValue().GetValue();
return m_cache->GetLibModificationTime().GetValue().GetValue();
} }
void LEGACY_PLUGIN::cacheLib( const wxString& aLibraryPath ) void LEGACY_PLUGIN::cacheLib( const wxString& aLibraryPath )
{ {
if( !m_cache || m_cache->m_lib_path != aLibraryPath || if( !m_cache || m_cache->m_lib_path != aLibraryPath || m_cache->IsModified() )
// somebody else on a network touched the library:
m_cache->m_mod_time != m_cache->GetLibModificationTime() )
{ {
// a spectacular episode in memory management: // a spectacular episode in memory management:
delete m_cache; delete m_cache;

View File

@ -192,7 +192,7 @@ MODULE* PCB_BASE_FRAME::SelectFootprintFromLibTree( bool aAllowBrowser )
static wxString lastComponentName; 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 ); GFootprintList.ReadFootprintFiles( fpTable, nullptr, &progressReporter );
progressReporter.Show( false ); progressReporter.Show( false );

View File

@ -28,6 +28,31 @@
#include <gal/graphics_abstraction_layer.h> #include <gal/graphics_abstraction_layer.h>
#include <class_board.h> #include <class_board.h>
#include <view/view.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 ) void PCB_BASE_EDIT_FRAME::SetRotationAngle( int aRotationAngle )

View File

@ -37,12 +37,9 @@ class PCB_BASE_EDIT_FRAME : public PCB_BASE_FRAME
public: public:
PCB_BASE_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent, FRAME_T aFrameType, PCB_BASE_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent, FRAME_T aFrameType,
const wxString& aTitle, const wxPoint& aPos, const wxSize& aSize, const wxString& aTitle, const wxPoint& aPos, const wxSize& aSize,
long aStyle, const wxString& aFrameName ) : long aStyle, const wxString& aFrameName );
PCB_BASE_FRAME( aKiway, aParent, aFrameType, aTitle, aPos, aSize, aStyle, aFrameName ),
m_rotationAngle( 900 ), m_undoRedoBlocked( false )
{}
virtual ~PCB_BASE_EDIT_FRAME() {}; virtual ~PCB_BASE_EDIT_FRAME();
/** /**
* Function GetModel() * Function GetModel()