summaryrefslogtreecommitdiff
path: root/new/sch_dir_lib_source.cpp
diff options
context:
space:
mode:
authorsaurabhb172020-02-26 16:20:48 +0530
committerGitHub2020-02-26 16:20:48 +0530
commitb77f5d9d8097c38159c6f60917995d6af13bbe1c (patch)
tree1392c90227aeea231c1d86371131e04c40382918 /new/sch_dir_lib_source.cpp
parentdadc4d490966a24efe15b5cc533ef8695986048a (diff)
parent003d02608917e7a69d1a98438837e94ccf68352a (diff)
downloadKiCad-eSim-b77f5d9d8097c38159c6f60917995d6af13bbe1c.tar.gz
KiCad-eSim-b77f5d9d8097c38159c6f60917995d6af13bbe1c.tar.bz2
KiCad-eSim-b77f5d9d8097c38159c6f60917995d6af13bbe1c.zip
Merge pull request #4 from FOSSEE/develop
merging dev into master
Diffstat (limited to 'new/sch_dir_lib_source.cpp')
-rw-r--r--new/sch_dir_lib_source.cpp732
1 files changed, 732 insertions, 0 deletions
diff --git a/new/sch_dir_lib_source.cpp b/new/sch_dir_lib_source.cpp
new file mode 100644
index 0000000..c91e1b5
--- /dev/null
+++ b/new/sch_dir_lib_source.cpp
@@ -0,0 +1,732 @@
+/*
+ * 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;
+
+
+/**
+ * Class 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; }
+};
+
+
+/**
+ * Class 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 ) throw( IO_ERROR )
+{
+ 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() throw( IO_ERROR )
+{
+ partnames.clear();
+ categories.clear();
+
+ cacheOneDir( "" );
+}
+
+
+DIR_LIB_SOURCE::DIR_LIB_SOURCE( const STRING& aDirectoryPath,
+ const STRING& aOptions ) throw( IO_ERROR ) :
+ 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 )
+ throw( IO_ERROR )
+{
+ 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 )
+ throw( IO_ERROR )
+{
+ 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 )
+ throw( IO_ERROR )
+{
+ 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 )
+ throw( IO_ERROR )
+{
+ 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 ) throw( IO_ERROR )
+{
+ 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 ) throw( IO_ERROR )
+{
+ 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