|
|
/*
* This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2010 SoftPLC Corporation, <dick@softplc.com> * Copyright (C) 2010 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 */
/* Note: this LIB_SOURCE implementation relies on the posix specified opendir() and
related functions rather than wx functions which might do the same thing. This is because I did not want to become very dependent on wxWidgets at such a low level as this, in case someday this code needs to be used on kde or whatever.
Mingw and unix, linux, & osx will all have these posix functions. MS Visual Studio may need the posix compatible opendir() functions brought in http://www.softagalleria.net/dirent.php
wx has these but they are based on wxString which can be wchar_t based and wx should not be introduced at a level this low.
Part files: have the general form partname.part[.revN...] Categories: are any subdirectories immediately below the sourceURI, one level only. Part names: [category/]partname[/revN...]*/
#include <dirent.h>
#include <sys/stat.h>
#include <cstring>
#include <cstdio>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <sch_dir_lib_source.h>
using namespace SCH;
/**
* DIR_WRAP * provides a destructor which is invoked if an exception is thrown. */class DIR_WRAP{ DIR* dir;
public: DIR_WRAP( DIR* aDir ) : dir( aDir ) {}
~DIR_WRAP() { if( dir ) closedir( dir ); }
DIR* operator->() { return dir; } DIR* operator*() { return dir; } operator bool () { return dir!=0; }};
/**
* FILE_WRAP * provides a destructor which is invoked if an exception is thrown. */class FILE_WRAP{ int fh;
public: FILE_WRAP( int aFileHandle ) : fh( aFileHandle ) {} ~FILE_WRAP() { if( fh != -1 ) close( fh ); }
operator int () { return fh; }};
/**
* Function strrstr * finds the last instance of needle in haystack, if any. */static const char* strrstr( const char* haystack, const char* needle ){ const char* ret = 0; const char* next = haystack;
// find last instance of haystack
while( (next = strstr( next, needle )) != 0 ) { ret = next; ++next; // don't keep finding the same one.
}
return ret;}
#if 1 // @todo switch over to EndsWithRev() global
static inline bool isDigit( char c ){ return c >= '0' && c <= '9';}
/**
* Function endsWithRev * returns a pointer to the final string segment: "revN[N..]" or NULL if none. * @param start is the beginning of string segment to test, the partname or * any middle portion of it. * @param tail is a pointer to the terminating nul, or one past inclusive end of * segment, i.e. the string segment of interest is [start,tail) * @param separator is the separating byte, expected: '.' or '/', depending on context. */static const char* endsWithRev( const char* start, const char* tail, char separator = '/' ){ bool sawDigit = false;
while( tail > start && isDigit( *--tail ) ) { sawDigit = true; }
// if sawDigit, tail points to the 'v' here.
if( sawDigit && tail-3 >= start ) { tail -= 3;
if( tail[0]==separator && tail[1]=='r' && tail[2]=='e' && tail[3]=='v' ) { return tail+1; // omit separator, return "revN[N..]"
} }
return 0;}
static inline const char* endsWithRev( const STRING& aPartName, char separator = '/' ){ return endsWithRev( aPartName.c_str(), aPartName.c_str()+aPartName.size(), separator );}
#endif
// see struct BY_REV
bool BY_REV::operator() ( const STRING& s1, const STRING& s2 ) const{ // avoid instantiating new STRINGs, and thank goodness that c_str() is const.
const char* rev1 = endsWithRev( s1 ); const char* rev2 = endsWithRev( s2 );
int rootLen1 = rev1 ? rev1 - s1.c_str() : s1.size(); int rootLen2 = rev2 ? rev2 - s2.c_str() : s2.size();
int r = memcmp( s1.c_str(), s2.c_str(), min( rootLen1, rootLen2 ) );
if( r ) { return r < 0; }
if( rootLen1 != rootLen2 ) { return rootLen1 < rootLen2; }
// root strings match at this point, compare the revision number numerically,
// and chose the higher numbered version as "less", according to std::set lingo.
if( bool(rev1) != bool(rev2) ) { return bool(rev1) < bool(rev2); }
if( rev1 && rev2 ) { int rnum1 = atoi( rev1+3 ); int rnum2 = atoi( rev2+3 );
// higher numbered revs are "less" so that they bubble to top.
return rnum1 > rnum2; }
return false; // strings are equal, and they don't have a rev
}
bool DIR_LIB_SOURCE::makePartName( STRING* aPartName, const char* aEntry, const STRING& aCategory ){ const char* cp = strrstr( aEntry, SWEET_EXT );
// if base name is not empty, contains SWEET_EXT, && cp is not NULL
if( cp > aEntry ) { const char* limit = cp + strlen( cp );
// If versioning, then must find a trailing "revN.." type of string.
if( useVersioning ) { const char* rev = endsWithRev( cp + SWEET_EXTZ, limit, '.' ); if( rev ) { if( aCategory.size() ) *aPartName = aCategory + "/"; else aPartName->clear();
aPartName->append( aEntry, cp - aEntry ); aPartName->append( "/" ); aPartName->append( rev ); return true; } }
// If using versioning, then all valid partnames must have a rev string,
// so we don't even bother to try and load any other partfile down here.
else { // if file extension is exactly SWEET_EXT, and no rev
if( cp==limit-5 ) { if( aCategory.size() ) *aPartName = aCategory + "/"; else aPartName->clear();
aPartName->append( aEntry, cp - aEntry ); return true; } } }
return false;}
STRING DIR_LIB_SOURCE::makeFileName( const STRING& aPartName ){ // create a fileName for the sweet string, using a reversible
// partname <-> fileName conversion protocol:
STRING fileName = sourceURI + "/";
const char* rev = endsWithRev( aPartName );
if( rev ) { int basePartLen = rev - aPartName.c_str() - 1; // omit '/' separator
fileName.append( aPartName, 0, basePartLen ); fileName += SWEET_EXT; fileName += '.'; fileName += rev; } else { fileName += aPartName; fileName += SWEET_EXT; }
return fileName;}
void DIR_LIB_SOURCE::readString( STRING* aResult, const STRING& aFileName ){ FILE_WRAP fw = open( aFileName.c_str(), O_RDONLY );
if( fw == -1 ) { STRING msg = strerror( errno ); msg += "; cannot open(O_RDONLY) file " + aFileName; THROW_IO_ERROR( msg ); }
struct stat fs;
fstat( fw, &fs );
// sanity check on file size
if( fs.st_size > (1*1024*1024) ) { STRING msg = aFileName; msg += " seems too big. ( > 1 mbyte )"; THROW_IO_ERROR( msg ); }
#if 0
// I read somewhere on the Internet that std::string chars are not guaranteed
// (over time) to be contiguous in future implementations of C++, so this
// strategy is here for that eventuality. We buffer through readBuffer here.
// reuse same readBuffer, which is not thread safe, but the API
// is not advertising thread safe (yet, if ever).
if( (int) fs.st_size > (int) readBuffer.size() ) readBuffer.resize( fs.st_size + 1000 );
int count = read( fw, &readBuffer[0], fs.st_size ); if( count != (int) fs.st_size ) { STRING msg = strerror( errno ); msg += "; cannot read file " + aFileName; THROW_IO_ERROR( msg ); }
aResult->assign( &readBuffer[0], count );#else
// read into the string directly
aResult->resize( fs.st_size );
int count = read( fw, &(*aResult)[0], fs.st_size ); if( count != (int) fs.st_size ) { STRING msg = strerror( errno ); msg += "; cannot read file " + aFileName; THROW_IO_ERROR( msg ); }
// test trailing nul is there, which should have been put there with resize() above
// printf( "'%s'\n", aResult->c_str() ); // checked OK.
#endif
}
void DIR_LIB_SOURCE::cache(){ partnames.clear(); categories.clear();
cacheOneDir( "" );}
DIR_LIB_SOURCE::DIR_LIB_SOURCE( const STRING& aDirectoryPath, const STRING& aOptions ) : useVersioning( strstr( aOptions.c_str(), "useVersioning" ) ){ sourceURI = aDirectoryPath; sourceType = "dir";
if( sourceURI.size() == 0 ) { THROW_IO_ERROR( STRING("aDirectoryPath cannot be empty") ); }
// remove any trailing separator, so we can add it back later without ambiguity
if( strchr( "/\\", sourceURI[sourceURI.size()-1] ) ) sourceURI.erase( sourceURI.size()-1 );
cache();}
DIR_LIB_SOURCE::~DIR_LIB_SOURCE(){}
void DIR_LIB_SOURCE::GetCategoricalPartNames( STRINGS* aResults, const STRING& aCategory ){ PN_ITER end = aCategory.size() ? partnames.lower_bound( aCategory + char( '/' + 1 ) ) : partnames.end();
PN_ITER it = aCategory.size() ? partnames.upper_bound( aCategory + "/" ) : partnames.begin();
aResults->clear();
if( useVersioning ) { STRING partName;
while( it != end ) { const char* rev = endsWithRev( *it );
// all cached partnames have a rev string in useVersioning mode
assert( rev );
// partName is substring which omits the rev AND the rev separator
partName.assign( *it, 0, rev - it->c_str() - 1 );
aResults->push_back( partName );
// skip over all other versions of the same partName.
it = partnames.lower_bound( partName + char( '/' + 1 ) ); }
}
else { while( it != end ) aResults->push_back( *it++ ); }}
void DIR_LIB_SOURCE::GetRevisions( STRINGS* aResults, const STRING& aPartName ){ aResults->clear();
if( useVersioning ) { STRING partName;
const char* rev = endsWithRev( aPartName ); if( rev ) // partName is substring which omits the rev and the separator
partName.assign( aPartName, 0, rev - aPartName.c_str() - 1 ); else partName = aPartName;
PN_ITER it = partnames.upper_bound( partName +'/' ); PN_ITER end = partnames.lower_bound( partName + char( '/' +1 ) );
for( ; it != end; ++it ) { const char* rev = endsWithRev( *it ); assert( rev ); aResults->push_back( it->substr( rev - it->c_str() ) ); } }
else { // In non-version mode, there were no revisions read in, only part
// files without a revision. But clients higher up expect to see
// at least one revision in order for the API to work, so we return
// a revision ""
aResults->push_back( "" ); }}
void DIR_LIB_SOURCE::ReadPart( STRING* aResult, const STRING& aPartName, const STRING& aRev ){ STRING partName = aPartName; const char* hasRev = endsWithRev( partName );
if( useVersioning ) { if( aRev.size() ) { // a supplied rev replaces any in aPartName
if( hasRev ) partName.resize( hasRev - partName.c_str() - 1 );
partName += '/'; partName + aRev;
// find this exact revision
PN_ITER it = partnames.find( partName );
if( it == partnames.end() ) // part not found
{ partName += " not found."; THROW_IO_ERROR( partName ); }
readString( aResult, makeFileName( partName ) ); }
else { // There's no rev on partName string. Find the most recent rev, i.e. highest,
// which will be first because of the BY_REV compare method, which treats
// higher numbered revs as first.
STRING search = partName + '/';
// There's no rev on partName string. Find the most recent rev, i.e. highest,
// which will be first because of the BY_REV compare method, which treats
// higher numbered revs as first.
PN_ITER it = partnames.upper_bound( search );
// verify that this one that is greater than partName is a match and not
// some unrelated name that is larger.
if( it == partnames.end() || it->compare( 0, search.size(), search ) != 0 ) { partName += " is not present without a revision."; THROW_IO_ERROR( partName ); }
readString( aResult, makeFileName( *it ) ); } }
else // !useVersioning
{#if 1
if( hasRev || aRev.size() ) { STRING msg = "this type 'dir' LIB_SOURCE not using 'useVersioning' option, cannot ask for a revision"; THROW_IO_ERROR( msg ); }#else
// no revisions allowed, strip it
if( hasRev ) partName.resize( hasRev - partName.c_str() - 1 );#endif
// find the part name without any revision
PN_ITER it = partnames.find( partName );
if( it == partnames.end() ) // part not found
{ partName += " not found."; THROW_IO_ERROR( partName ); }
readString( aResult, makeFileName( partName ) ); }}
void DIR_LIB_SOURCE::ReadParts( STRINGS* aResults, const STRINGS& aPartNames ){ aResults->clear();
for( STRINGS::const_iterator n = aPartNames.begin(); n!=aPartNames.end(); ++n ) { aResults->push_back( STRING() ); ReadPart( &aResults->back(), *n ); }}
void DIR_LIB_SOURCE::GetCategories( STRINGS* aResults ){ aResults->clear();
// caller fetches them sorted.
for( NAME_CACHE::const_iterator it = categories.begin(); it!=categories.end(); ++it ) { aResults->push_back( *it ); }}
#if defined(DEBUG)
void DIR_LIB_SOURCE::Show(){ printf( "Show categories:\n" ); for( NAME_CACHE::const_iterator it = categories.begin(); it!=categories.end(); ++it ) printf( " '%s'\n", it->c_str() );
printf( "\n" ); printf( "Show parts:\n" ); for( PART_CACHE::const_iterator it = partnames.begin(); it != partnames.end(); ++it ) { printf( " '%s'\n", it->c_str() ); }}
#endif
void DIR_LIB_SOURCE::cacheOneDir( const STRING& aCategory ){ STRING curDir = sourceURI;
if( aCategory.size() ) curDir += "/" + aCategory;
DIR_WRAP dir = opendir( curDir.c_str() );
if( !dir ) { STRING msg = strerror( errno ); msg += "; scanning directory " + curDir; THROW_IO_ERROR( msg ); }
struct stat fs; STRING partName; STRING fileName; dirent* entry;
while( (entry = readdir( *dir )) != NULL ) { if( !strcmp( ".", entry->d_name ) || !strcmp( "..", entry->d_name ) ) continue;
fileName = curDir + "/" + entry->d_name;
if( !stat( fileName.c_str(), &fs ) ) { // is this a valid part name?
if( S_ISREG( fs.st_mode ) && makePartName( &partName, entry->d_name, aCategory ) ) { std::pair<NAME_CACHE::iterator, bool> pair = partnames.insert( partName );
if( !pair.second ) { STRING msg = partName; msg += " has already been encountered"; THROW_IO_ERROR( msg ); } }
// is this an acceptable category name?
else if( S_ISDIR( fs.st_mode ) && !aCategory.size() && isCategoryName( entry->d_name ) ) { // only one level of recursion is used, controlled by the
// emptiness of aCategory.
categories.insert( entry->d_name );
// somebody needs to test Windows (mingw), make sure it can
// handle opendir() recursively
cacheOneDir( entry->d_name ); } else { //D( printf( "ignoring %s\n", entry->d_name );)
} } }}
#if 0 && defined(DEBUG)
void DIR_LIB_SOURCE::Test( int argc, char** argv ){ STRINGS partnames; STRINGS sweets;
try { STRINGS::const_iterator pn;
// DIR_LIB_SOURCE uut( argv[1] ? argv[1] : "", "" );
DIR_LIB_SOURCE uut( argv[1] ? argv[1] : "", "useVersioning" );
// show the cached content, only the directory information is cached,
// parts are cached in class LIB, not down here.
uut.Show();
uut.GetCategoricalPartNames( &partnames, "lions" );
printf( "\nGetCategoricalPartNames( aCatagory = 'lions' ):\n" ); for( STRINGS::const_iterator it = partnames.begin(); it!=partnames.end(); ++it ) { printf( " '%s'\n", it->c_str() ); }
uut.ReadParts( &sweets, partnames );
printf( "\nSweets for Category = 'lions' parts:\n" ); pn = partnames.begin(); for( STRINGS::const_iterator it = sweets.begin(); it!=sweets.end(); ++it, ++pn ) { printf( " %s: %s", pn->c_str(), it->c_str() ); }
// fetch the part names for ALL categories.
uut.GetCategoricalPartNames( &partnames );
printf( "\nGetCategoricalPartNames( aCategory = '' i.e. ALL):\n" ); for( STRINGS::const_iterator it = partnames.begin(); it!=partnames.end(); ++it ) { printf( " '%s'\n", it->c_str() ); }
uut.ReadParts( &sweets, partnames );
printf( "\nSweets for ALL parts:\n" ); pn = partnames.begin(); for( STRINGS::const_iterator it = sweets.begin(); it!=sweets.end(); ++it, ++pn ) { printf( " %s: %s", pn->c_str(), it->c_str() ); } }
catch( std::exception& ex ) { printf( "std::exception\n" ); }
catch( const IO_ERROR& ioe ) { printf( "exception: %s\n", (const char*) ioe.errorText.ToUTF8() ) ); }}
int main( int argc, char** argv ){ DIR_LIB_SOURCE::Test( argc, argv ); return 0;}
#endif
|