/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2010 SoftPLC Corporation, * 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 #include #include #include #include #include #include #include #include #include #include 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 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