diff options
Diffstat (limited to 'common/fp_lib_table.cpp')
-rw-r--r-- | common/fp_lib_table.cpp | 789 |
1 files changed, 789 insertions, 0 deletions
diff --git a/common/fp_lib_table.cpp b/common/fp_lib_table.cpp new file mode 100644 index 0000000..d9fc1e7 --- /dev/null +++ b/common/fp_lib_table.cpp @@ -0,0 +1,789 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright (C) 2010-2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com> + * Copyright (C) 2012 Wayne Stambaugh <stambaughw@verizon.net> + * Copyright (C) 2012-2015 KiCad Developers, see change_log.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 2 + * 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, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + + +#include <fctsys.h> +#include <wx/config.h> // wxExpandEnvVars() +#include <wx/filename.h> +#include <wx/stdpaths.h> + +#include <set> + +//#include <pgm_base.h> +#include <kiface_i.h> +#include <search_stack.h> +#include <pcb_netlist.h> +#include <reporter.h> +#include <footprint_info.h> +#include <wildcards_and_files_ext.h> +#include <fpid.h> +#include <fp_lib_table_lexer.h> +#include <fp_lib_table.h> +#include <class_module.h> + +using namespace FP_LIB_TABLE_T; + + +static const wxChar global_tbl_name[] = wxT( "fp-lib-table" ); + + +void FP_LIB_TABLE::ROW::SetType( const wxString& aType ) +{ + type = IO_MGR::EnumFromStr( aType ); + + if( IO_MGR::PCB_FILE_T( -1 ) == type ) + type = IO_MGR::KICAD; +} + + +void FP_LIB_TABLE::ROW::SetFullURI( const wxString& aFullURI ) +{ + uri_user = aFullURI; + +#if !FP_LATE_ENVVAR + uri_expanded = FP_LIB_TABLE::ExpandSubstitutions( aFullURI ); +#endif +} + + +const wxString FP_LIB_TABLE::ROW::GetFullURI( bool aSubstituted ) const +{ + if( aSubstituted ) + { +#if !FP_LATE_ENVVAR // early expansion + return uri_expanded; + +#else // late expansion + return FP_LIB_TABLE::ExpandSubstitutions( uri_user ); +#endif + } + else + return uri_user; +} + + +FP_LIB_TABLE::ROW::ROW( const ROW& a ) : + nickName( a.nickName ), + type( a.type ), + options( a.options ), + description( a.description ), + properties( 0 ) +{ + // may call ExpandSubstitutions() + SetFullURI( a.uri_user ); + + if( a.properties ) + properties = new PROPERTIES( *a.properties ); +} + + +FP_LIB_TABLE::ROW& FP_LIB_TABLE::ROW::operator=( const ROW& r ) +{ + nickName = r.nickName; + type = r.type; + options = r.options; + description = r.description; + properties = r.properties ? new PROPERTIES( *r.properties ) : NULL; + + // may call ExpandSubstitutions() + SetFullURI( r.uri_user ); + + // Do not copy the PLUGIN, it is lazily created. Delete any existing + // destination plugin. + setPlugin( NULL ); + + return *this; +} + + +bool FP_LIB_TABLE::ROW::operator==( const ROW& r ) const +{ + return nickName == r.nickName + && uri_user == r.uri_user + && type == r.type + && options == r.options + && description == r.description + ; +} + + +FP_LIB_TABLE::FP_LIB_TABLE( FP_LIB_TABLE* aFallBackTable ) : + fallBack( aFallBackTable ) +{ + // not copying fall back, simply search aFallBackTable separately + // if "nickName not found". +} + + +FP_LIB_TABLE::~FP_LIB_TABLE() +{ + // *fallBack is not owned here. +} + + +wxArrayString FP_LIB_TABLE::FootprintEnumerate( const wxString& aNickname ) +{ + const ROW* row = FindRow( aNickname ); + wxASSERT( (PLUGIN*) row->plugin ); + return row->plugin->FootprintEnumerate( row->GetFullURI( true ), row->GetProperties() ); +} + + +MODULE* FP_LIB_TABLE::FootprintLoad( const wxString& aNickname, const wxString& aFootprintName ) +{ + const ROW* row = FindRow( aNickname ); + wxASSERT( (PLUGIN*) row->plugin ); + + MODULE* ret = row->plugin->FootprintLoad( row->GetFullURI( true ), aFootprintName, row->GetProperties() ); + + // The library cannot know its own name, because it might have been renamed or moved. + // Therefore footprints cannot know their own library nickname when residing in + // a footprint library. + // Only at this API layer can we tell the footprint about its actual library nickname. + if( ret ) + { + // remove "const"-ness, I really do want to set nickname without + // having to copy the FPID and its two strings, twice each. + FPID& fpid = (FPID&) ret->GetFPID(); + + // Catch any misbehaving plugin, which should be setting internal footprint name properly: + wxASSERT( aFootprintName == (wxString) fpid.GetFootprintName() ); + + // and clearing nickname + wxASSERT( !fpid.GetLibNickname().size() ); + + fpid.SetLibNickname( row->GetNickName() ); + } + + return ret; +} + + +FP_LIB_TABLE::SAVE_T FP_LIB_TABLE::FootprintSave( const wxString& aNickname, const MODULE* aFootprint, bool aOverwrite ) +{ + const ROW* row = FindRow( aNickname ); + wxASSERT( (PLUGIN*) row->plugin ); + + if( !aOverwrite ) + { + // Try loading the footprint to see if it already exists, caller wants overwrite + // protection, which is atypical, not the default. + + wxString fpname = aFootprint->GetFPID().GetFootprintName(); + + std::auto_ptr<MODULE> m( row->plugin->FootprintLoad( row->GetFullURI( true ), fpname, row->GetProperties() ) ); + + if( m.get() ) + return SAVE_SKIPPED; + } + + row->plugin->FootprintSave( row->GetFullURI( true ), aFootprint, row->GetProperties() ); + + return SAVE_OK; +} + + +void FP_LIB_TABLE::FootprintDelete( const wxString& aNickname, const wxString& aFootprintName ) +{ + const ROW* row = FindRow( aNickname ); + wxASSERT( (PLUGIN*) row->plugin ); + return row->plugin->FootprintDelete( row->GetFullURI( true ), aFootprintName, row->GetProperties() ); +} + + +bool FP_LIB_TABLE::IsFootprintLibWritable( const wxString& aNickname ) +{ + const ROW* row = FindRow( aNickname ); + wxASSERT( (PLUGIN*) row->plugin ); + return row->plugin->IsFootprintLibWritable( row->GetFullURI( true ) ); +} + + +void FP_LIB_TABLE::FootprintLibDelete( const wxString& aNickname ) +{ + const ROW* row = FindRow( aNickname ); + wxASSERT( (PLUGIN*) row->plugin ); + row->plugin->FootprintLibDelete( row->GetFullURI( true ), row->GetProperties() ); +} + + +void FP_LIB_TABLE::FootprintLibCreate( const wxString& aNickname ) +{ + const ROW* row = FindRow( aNickname ); + wxASSERT( (PLUGIN*) row->plugin ); + row->plugin->FootprintLibCreate( row->GetFullURI( true ), row->GetProperties() ); +} + + +const wxString FP_LIB_TABLE::GetDescription( const wxString& aNickname ) +{ + // use "no exception" form of find row: + const ROW* row = findRow( aNickname ); + if( row ) + return row->description; + else + return wxEmptyString; +} + + +void FP_LIB_TABLE::Parse( FP_LIB_TABLE_LEXER* in ) throw( IO_ERROR, PARSE_ERROR ) +{ + /* + (fp_lib_table + (lib (name NICKNAME)(descr DESCRIPTION)(type TYPE)(full_uri FULL_URI)(options OPTIONS)) + : + ) + + Elements after (name) are order independent. + */ + + T tok; + wxString errMsg; // to collect error messages + + // This table may be nested within a larger s-expression, or not. + // Allow for parser of that optional containing s-epression to have looked ahead. + if( in->CurTok() != T_fp_lib_table ) + { + in->NeedLEFT(); + if( ( tok = in->NextTok() ) != T_fp_lib_table ) + in->Expecting( T_fp_lib_table ); + } + + while( ( tok = in->NextTok() ) != T_RIGHT ) + { + ROW row; // reconstructed for each row in input stream. + + if( tok == T_EOF ) + in->Expecting( T_RIGHT ); + + if( tok != T_LEFT ) + in->Expecting( T_LEFT ); + + if( ( tok = in->NextTok() ) != T_lib ) + in->Expecting( T_lib ); + + // (name NICKNAME) + in->NeedLEFT(); + + if( ( tok = in->NextTok() ) != T_name ) + in->Expecting( T_name ); + + in->NeedSYMBOLorNUMBER(); + + row.SetNickName( in->FromUTF8() ); + + in->NeedRIGHT(); + + // After (name), remaining (lib) elements are order independent, and in + // some cases optional. + bool sawType = false; + bool sawOpts = false; + bool sawDesc = false; + bool sawUri = false; + + while( ( tok = in->NextTok() ) != T_RIGHT ) + { + if( tok == T_EOF ) + in->Unexpected( T_EOF ); + + if( tok != T_LEFT ) + in->Expecting( T_LEFT ); + + tok = in->NeedSYMBOLorNUMBER(); + + switch( tok ) + { + case T_uri: + if( sawUri ) + in->Duplicate( tok ); + sawUri = true; + in->NeedSYMBOLorNUMBER(); + // Saved path and file names use the Unix notation (separator = '/') + // However old files, and files edited by hand can use the windows + // separator. Force the unix notation. + // (It works on windows, and moreover, wxFileName and wxDir takes care to that + // on windows) + // moreover, URLs use the '/' as separator + { + wxString uri = in->FromUTF8(); + uri.Replace( '\\', '/' ); + row.SetFullURI( uri ); + } + break; + + case T_type: + if( sawType ) + in->Duplicate( tok ); + sawType = true; + in->NeedSYMBOLorNUMBER(); + row.SetType( in->FromUTF8() ); + break; + + case T_options: + if( sawOpts ) + in->Duplicate( tok ); + sawOpts = true; + in->NeedSYMBOLorNUMBER(); + row.SetOptions( in->FromUTF8() ); + break; + + case T_descr: + if( sawDesc ) + in->Duplicate( tok ); + sawDesc = true; + in->NeedSYMBOLorNUMBER(); + row.SetDescr( in->FromUTF8() ); + break; + + default: + in->Unexpected( tok ); + } + + in->NeedRIGHT(); + } + + if( !sawType ) + in->Expecting( T_type ); + + if( !sawUri ) + in->Expecting( T_uri ); + + // all nickNames within this table fragment must be unique, so we do not + // use doReplace in InsertRow(). (However a fallBack table can have a + // conflicting nickName and ours will supercede that one since in + // FindLib() we search this table before any fall back.) + if( !InsertRow( row ) ) + { + wxString msg = wxString::Format( + _( "'%s' is a duplicate footprint library nickName" ), + GetChars( row.nickName ) ); + if( !errMsg.IsEmpty() ) + errMsg << '\n'; + + errMsg << msg; + } + } + + if( !errMsg.IsEmpty() ) + THROW_IO_ERROR( errMsg ); +} + + +void FP_LIB_TABLE::Format( OUTPUTFORMATTER* out, int nestLevel ) const + throw( IO_ERROR, boost::interprocess::lock_exception ) +{ + out->Print( nestLevel, "(fp_lib_table\n" ); + + for( ROWS_CITER it = rows.begin(); it != rows.end(); ++it ) + it->Format( out, nestLevel+1 ); + + out->Print( nestLevel, ")\n" ); +} + + +void FP_LIB_TABLE::ROW::Format( OUTPUTFORMATTER* out, int nestLevel ) const + throw( IO_ERROR, boost::interprocess::lock_exception ) +{ + // In Kicad, we save path and file names using the Unix notation (separator = '/') + // So ensure separator is always '/' is saved URI string + wxString uri = GetFullURI(); + uri.Replace( '\\', '/' ); + + out->Print( nestLevel, "(lib (name %s)(type %s)(uri %s)(options %s)(descr %s))\n", + out->Quotew( GetNickName() ).c_str(), + out->Quotew( GetType() ).c_str(), + out->Quotew( uri ).c_str(), + out->Quotew( GetOptions() ).c_str(), + out->Quotew( GetDescr() ).c_str() + ); +} + +#define OPT_SEP '|' ///< options separator character + +PROPERTIES* FP_LIB_TABLE::ParseOptions( const std::string& aOptionsList ) +{ + if( aOptionsList.size() ) + { + const char* cp = &aOptionsList[0]; + const char* end = cp + aOptionsList.size(); + + PROPERTIES props; + std::string pair; + + // Parse all name=value pairs + while( cp < end ) + { + pair.clear(); + + // Skip leading white space. + while( cp < end && isspace( *cp ) ) + ++cp; + + // Find the end of pair/field + while( cp < end ) + { + if( *cp=='\\' && cp+1<end && cp[1]==OPT_SEP ) + { + ++cp; // skip the escape + pair += *cp++; // add the separator + } + else if( *cp==OPT_SEP ) + { + ++cp; // skip the separator + break; // process the pair + } + else + pair += *cp++; + } + + // stash the pair + if( pair.size() ) + { + // first equals sign separates 'name' and 'value'. + size_t eqNdx = pair.find( '=' ); + if( eqNdx != pair.npos ) + { + std::string name = pair.substr( 0, eqNdx ); + std::string value = pair.substr( eqNdx + 1 ); + props[name] = value; + } + else + props[pair] = ""; // property is present, but with no value. + } + } + + if( props.size() ) + return new PROPERTIES( props ); + } + return NULL; +} + + +UTF8 FP_LIB_TABLE::FormatOptions( const PROPERTIES* aProperties ) +{ + UTF8 ret; + + if( aProperties ) + { + for( PROPERTIES::const_iterator it = aProperties->begin(); it != aProperties->end(); ++it ) + { + const std::string& name = it->first; + + const UTF8& value = it->second; + + if( ret.size() ) + ret += OPT_SEP; + + ret += name; + + // the separation between name and value is '=' + if( value.size() ) + { + ret += '='; + + for( std::string::const_iterator si = value.begin(); si != value.end(); ++si ) + { + // escape any separator in the value. + if( *si == OPT_SEP ) + ret += '\\'; + + ret += *si; + } + } + } + } + + return ret; +} + + +std::vector<wxString> FP_LIB_TABLE::GetLogicalLibs() +{ + // Only return unique logical library names. Use std::set::insert() to + // quietly reject any duplicates, which can happen when encountering a duplicate + // nickname from one of the fall back table(s). + + std::set<wxString> unique; + std::vector<wxString> ret; + const FP_LIB_TABLE* cur = this; + + do + { + for( ROWS_CITER it = cur->rows.begin(); it!=cur->rows.end(); ++it ) + { + unique.insert( it->nickName ); + } + + } while( ( cur = cur->fallBack ) != 0 ); + + ret.reserve( unique.size() ); + + // DBG(printf( "%s: count:%zd\n", __func__, unique.size() );) + + // return a sorted, unique set of nicknames in a std::vector<wxString> to caller + for( std::set<wxString>::const_iterator it = unique.begin(); it!=unique.end(); ++it ) + { + //DBG(printf( " %s\n", TO_UTF8( *it ) );) + ret.push_back( *it ); + } + + return ret; +} + + +FP_LIB_TABLE::ROW* FP_LIB_TABLE::findRow( const wxString& aNickName ) const +{ + FP_LIB_TABLE* cur = (FP_LIB_TABLE*) this; + + do + { + cur->ensureIndex(); + + INDEX_CITER it = cur->nickIndex.find( aNickName ); + + if( it != cur->nickIndex.end() ) + { + return &cur->rows[it->second]; // found + } + + // not found, search fall back table(s), if any + } while( ( cur = cur->fallBack ) != 0 ); + + return 0; // not found +} + + +const FP_LIB_TABLE::ROW* FP_LIB_TABLE::FindRowByURI( const wxString& aURI ) +{ + FP_LIB_TABLE* cur = this; + + do + { + cur->ensureIndex(); + + for( unsigned i = 0; i < cur->rows.size(); i++ ) + { + wxString uri = cur->rows[i].GetFullURI( true ); + + if( wxFileName::GetPathSeparator() == wxChar( '\\' ) && uri.Find( wxChar( '/' ) ) >= 0 ) + uri.Replace( wxT( "/" ), wxT( "\\" ) ); + + if( (wxFileName::IsCaseSensitive() && uri == aURI) + || (!wxFileName::IsCaseSensitive() && uri.Upper() == aURI.Upper() ) ) + { + return &cur->rows[i]; // found + } + } + + // not found, search fall back table(s), if any + } while( ( cur = cur->fallBack ) != 0 ); + + return 0; // not found +} + + +bool FP_LIB_TABLE::InsertRow( const ROW& aRow, bool doReplace ) +{ + ensureIndex(); + + INDEX_CITER it = nickIndex.find( aRow.nickName ); + + if( it == nickIndex.end() ) + { + rows.push_back( aRow ); + nickIndex.insert( INDEX_VALUE( aRow.nickName, rows.size() - 1 ) ); + return true; + } + + if( doReplace ) + { + rows[it->second] = aRow; + return true; + } + + return false; +} + + +const FP_LIB_TABLE::ROW* FP_LIB_TABLE::FindRow( const wxString& aNickname ) + throw( IO_ERROR ) +{ + ROW* row = findRow( aNickname ); + + if( !row ) + { + wxString msg = wxString::Format( + _( "fp-lib-table files contain no lib with nickname '%s'" ), + GetChars( aNickname ) ); + + THROW_IO_ERROR( msg ); + } + + // We've been 'lazy' up until now, but it cannot be deferred any longer, + // instantiate a PLUGIN of the proper kind if it is not already in this ROW. + if( !row->plugin ) + row->setPlugin( IO_MGR::PluginFind( row->type ) ); + + return row; +} + + +// wxGetenv( wchar_t* ) is not re-entrant on linux. +// Put a lock on multithreaded use of wxGetenv( wchar_t* ), called from wxEpandEnvVars(), +// needed by bool ReadFootprintFiles( FP_LIB_TABLE* aTable, const wxString* aNickname = NULL ); +#include <ki_mutex.h> + +const wxString FP_LIB_TABLE::ExpandSubstitutions( const wxString& aString ) +{ +// Duplicate code: the same is now in common.cpp, due to the fact it is used +// in many other places than FP_LIB_TABLE +#if 0 + static MUTEX getenv_mutex; + + MUTLOCK lock( getenv_mutex ); + + // We reserve the right to do this another way, by providing our own member + // function. + return wxExpandEnvVars( aString ); +#else + return ExpandEnvVarSubstitutions( aString ); +#endif +} + + +bool FP_LIB_TABLE::IsEmpty( bool aIncludeFallback ) +{ + if( !aIncludeFallback || !fallBack ) + return rows.empty(); + + return rows.empty() && fallBack->IsEmpty( true ); +} + + +MODULE* FP_LIB_TABLE::FootprintLoadWithOptionalNickname( const FPID& aFootprintId ) + throw( IO_ERROR, PARSE_ERROR, boost::interprocess::lock_exception ) +{ + wxString nickname = aFootprintId.GetLibNickname(); + wxString fpname = aFootprintId.GetFootprintName(); + + if( nickname.size() ) + { + return FootprintLoad( nickname, fpname ); + } + + // nickname is empty, sequentially search (alphabetically) all libs/nicks for first match: + else + { + std::vector<wxString> nicks = GetLogicalLibs(); + + // Search each library going through libraries alphabetically. + for( unsigned i = 0; i<nicks.size(); ++i ) + { + // FootprintLoad() returns NULL on not found, does not throw exception + // unless there's an IO_ERROR. + MODULE* ret = FootprintLoad( nicks[i], fpname ); + if( ret ) + return ret; + } + + return NULL; + } +} + + +const wxString FP_LIB_TABLE::GlobalPathEnvVariableName() +{ + return wxT( "KISYSMOD" ); +} + + +bool FP_LIB_TABLE::LoadGlobalTable( FP_LIB_TABLE& aTable ) + throw (IO_ERROR, PARSE_ERROR, boost::interprocess::lock_exception ) +{ + bool tableExists = true; + wxFileName fn = GetGlobalTableFileName(); + + if( !fn.FileExists() ) + { + tableExists = false; + + if( !fn.DirExists() && !fn.Mkdir( 0x777, wxPATH_MKDIR_FULL ) ) + { + THROW_IO_ERROR( wxString::Format( _( "Cannot create global library table path '%s'." ), + GetChars( fn.GetPath() ) ) ); + } + + // Attempt to copy the default global file table from the KiCad + // template folder to the user's home configuration path. + wxString fileName = Kiface().KifaceSearch().FindValidPath( global_tbl_name ); + + // The fallback is to create an empty global footprint table for the user to populate. + if( fileName.IsEmpty() || !::wxCopyFile( fileName, fn.GetFullPath(), false ) ) + { + FP_LIB_TABLE emptyTable; + + emptyTable.Save( fn.GetFullPath() ); + } + } + + aTable.Load( fn.GetFullPath() ); + + return tableExists; +} + + +wxString FP_LIB_TABLE::GetGlobalTableFileName() +{ + wxFileName fn; + + fn.SetPath( GetKicadConfigPath() ); + fn.SetName( global_tbl_name ); + + return fn.GetFullPath(); +} + +// prefer wxString filename so it can be seen in a debugger easier than wxFileName. + +void FP_LIB_TABLE::Load( const wxString& aFileName ) + throw( IO_ERROR ) +{ + // It's OK if footprint library tables are missing. + if( wxFileName::IsFileReadable( aFileName ) ) + { + FILE_LINE_READER reader( aFileName ); + FP_LIB_TABLE_LEXER lexer( &reader ); + + Parse( &lexer ); + } +} + + +void FP_LIB_TABLE::Save( const wxString& aFileName ) + const throw( IO_ERROR, boost::interprocess::lock_exception ) +{ + FILE_OUTPUTFORMATTER sf( aFileName ); + Format( &sf, 0 ); +} + |