kicad/3d-viewer/3d_cache/3d_filename_resolver.cpp

475 lines
12 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2015 Cirilo Bernardo <cirilo.bernardo@gmail.com>
*
* 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 <iostream>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <wx/filename.h>
#include <wx/utils.h>
#include "3d_filename_resolver.h"
#define S3D_RESOLVER_CONFIG wxT( "3Dresolver.cfg" )
bool S3D_FILENAME_RESOLVER::Set3DConfigDir( const wxString& aConfigDir )
{
if( aConfigDir.empty() )
return false;
wxFileName cfgdir( aConfigDir, wxT( "" ) );
cfgdir.Normalize();
if( false == cfgdir.DirExists() )
return false;
m_ConfigDir = cfgdir.GetPath();
createPathList();
return true;
}
bool S3D_FILENAME_RESOLVER::SetProjectDir( const wxString& aProjDir, bool* flgChanged )
{
if( aProjDir.empty() )
return false;
wxFileName projdir( aProjDir, wxT( "" ) );
projdir.Normalize();
if( false == projdir.DirExists() )
return false;
wxString path = projdir.GetPath();
if( flgChanged )
*flgChanged = false;
if( m_Paths.empty() )
{
m_Paths.push_back( path );
if( flgChanged )
*flgChanged = true;
}
else
{
if( m_Paths.front().Cmp( path ) )
{
m_Paths.pop_front();
m_Paths.push_front( path );
m_NameMap.clear();
if( flgChanged )
*flgChanged = true;
}
else
{
return true;
}
}
#ifdef DEBUG
std::cout << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
std::cout << " * [INFO] changed project dir to " << m_Paths.front().ToUTF8() << "\n";
#endif
return true;
}
wxString S3D_FILENAME_RESOLVER::GetProjectDir( void )
{
if( m_Paths.empty() )
return wxEmptyString;
return m_Paths.front();
}
bool S3D_FILENAME_RESOLVER::createPathList( void )
{
if( !m_Paths.empty() )
return true;
wxString kmod;
// add the current working directory as the first entry by
// default; since CWD is not necessarily what we really want,
// the user may change this later with a call to SetProjectDir()
if( !addPath( wxFileName::GetCwd() ) )
m_Paths.push_back( wxEmptyString );
if( wxGetEnv( wxT( "KISYS3DMOD" ), &kmod ) )
addPath( kmod );
if( !m_ConfigDir.empty() )
readPathList();
if( m_Paths.empty() )
return false;
#ifdef DEBUG
std::cout << " * [3D model] search paths:\n";
std::list< wxString >::const_iterator sPL = m_Paths.begin();
std::list< wxString >::const_iterator ePL = m_Paths.end();
while( sPL != ePL )
{
std::cout << " + '" << (*sPL).ToUTF8() << "'\n";
++sPL;
}
#endif
return true;
}
bool S3D_FILENAME_RESOLVER::UpdatePathList( std::vector< wxString >& aPathList )
{
while( m_Paths.size() > 1 )
m_Paths.pop_back();
size_t nI = aPathList.size();
for( size_t i = 0; i < nI; ++i )
addPath( aPathList[i] );
#ifdef DEBUG
std::cerr << "* S3D_FILENAME_RESOLVER::UpdatePathList()\n";
std::cerr << "NItems: " << aPathList.size() << "\n";
for( size_t i = 0; i < aPathList.size(); ++i )
std::cerr << "Item #" << i << ": " << aPathList[i].ToUTF8() << "\n";
#endif
return writePathList();
}
wxString S3D_FILENAME_RESOLVER::ResolvePath( const wxString& aFileName )
{
if( aFileName.empty() )
return wxEmptyString;
if( m_Paths.empty() )
createPathList();
// first attempt to use the name as specified:
wxString aResolvedName;
wxString fname;
// normalize paths with "${VARNAME}" to support legacy behavior
if( aFileName.StartsWith( wxT( "${" ) ) )
{
wxFileName tmp( aFileName );
if( tmp.Normalize() )
fname = tmp.GetFullPath();
else
fname = aFileName;
}
else
{
fname = aFileName;
}
#ifdef _WIN32
// translate from KiCad's internal UNIX-like path to MSWin paths
fname.Replace( wxT( "/" ), wxT( "\\" ) );
#endif
if( checkRealPath( fname, aResolvedName ) )
return aResolvedName;
// look up the filename in the internal filename map
std::map< wxString, wxString, S3D::rsort_wxString >::iterator mi;
mi = m_NameMap.find( fname );
if( mi != m_NameMap.end() )
return mi->second;
std::list< wxString >::const_iterator sPL = m_Paths.begin();
std::list< wxString >::const_iterator ePL = m_Paths.end();
while( sPL != ePL )
{
wxFileName fpath( wxFileName::DirName( *sPL ) );
wxFileName filename( fname );
// we can only attempt a search if the filename is incomplete
if( filename.IsRelative() )
{
wxString fullPath = fpath.GetPathWithSep() + fname;
if( checkRealPath( fullPath, aResolvedName ) )
return aResolvedName;
}
++sPL;
}
std::cerr << " * [3D Model] filename could not be resolved: '";
std::cerr << aFileName.ToUTF8() << "'\n";
return wxEmptyString;
}
bool S3D_FILENAME_RESOLVER::checkRealPath( const wxString& aFileName,
wxString& aResolvedName )
{
aResolvedName.clear();
wxFileName fname( aFileName );
fname.Normalize();
if( !fname.FileExists() )
return false;
aResolvedName = fname.GetFullPath();
m_NameMap.insert( std::pair< wxString, wxString > ( aFileName, aResolvedName ) );
return true;
}
bool S3D_FILENAME_RESOLVER::addPath( const wxString& aPath )
{
if( aPath.empty() )
return false;
wxFileName path( aPath, wxT( "" ) );
path.Normalize();
if( !path.DirExists() )
{
std::cerr << " * [3D Model] invalid path: '" << path.GetPath().ToUTF8() << "'\n";
return false;
}
wxString pname = path.GetPath();
std::list< wxString >::const_iterator sPL = m_Paths.begin();
std::list< wxString >::const_iterator ePL = m_Paths.end();
while( sPL != ePL )
{
if( !pname.Cmp( *sPL ) )
return true;
++sPL;
}
m_Paths.push_back( pname );
return true;
}
bool S3D_FILENAME_RESOLVER::readPathList( void )
{
if( m_ConfigDir.empty() )
{
std::cerr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
std::cerr << " * 3D configuration directory is unknown\n";
return false;
}
wxFileName cfgpath( m_ConfigDir, S3D_RESOLVER_CONFIG );
cfgpath.Normalize();
wxString cfgname = cfgpath.GetFullPath();
size_t nitems = m_Paths.size();
std::ifstream cfgFile;
std::string cfgLine;
if( !wxFileName::Exists( cfgname ) )
{
std::cerr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
std::cerr << " * no 3D configuration file: '";
std::cerr << cfgname.ToUTF8() << "'\n";
return false;
}
cfgFile.open( cfgname.ToUTF8() );
if( !cfgFile.is_open() )
{
std::cerr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
std::cerr << " * could not open configuration file '" << cfgname.ToUTF8() << "'\n";
return false;
}
int lineno = 0;
bool mod = false; // set to true if there are non-existent paths in the file
while( cfgFile.good() )
{
cfgLine.clear();
std::getline( cfgFile, cfgLine );
++lineno;
if( cfgLine.empty() )
{
if( cfgFile.eof() )
break;
continue;
}
std::string::size_type spos = cfgLine.find_first_of( '"', 0 );
if( std::string::npos == spos )
{
std::cerr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
std::cerr << " * bad config entry in config file '" << cfgname.ToUTF8() << "'\n";
std::cerr << " line " << lineno << " [missing opening quote mark]\n";
}
cfgLine.erase( 0, spos + 1 );
spos = cfgLine.find_last_of( '"' );
if( std::string::npos == spos )
{
std::cerr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
std::cerr << " * bad config entry in config file '" << cfgname.ToUTF8() << "'\n";
std::cerr << " line " << lineno << " [missing closing quote mark]\n";
}
cfgLine.erase( spos );
if( !addPath( cfgLine ) )
{
std::cerr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
std::cerr << " * bad config entry in config file '" << cfgname.ToUTF8() << "'\n";
std::cerr << " line " << lineno << " [not a valid path]: '";
std::cerr << cfgLine << "'\n";
mod = true;
}
}
cfgFile.close();
if( mod )
writePathList();
if( m_Paths.size() != nitems )
return true;
return false;
}
bool S3D_FILENAME_RESOLVER::writePathList( void )
{
if( m_ConfigDir.empty() )
{
std::cerr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
std::cerr << " * 3D configuration directory is unknown\n";
return false;
}
if( m_Paths.empty() || 1 == m_Paths.size() )
return false;
wxFileName cfgpath( m_ConfigDir, S3D_RESOLVER_CONFIG );
wxString cfgname = cfgpath.GetFullPath();
std::ofstream cfgFile;
cfgFile.open( cfgname.ToUTF8(), std::ios_base::trunc );
if( !cfgFile.is_open() )
{
std::cerr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
std::cerr << " * could not open configuration file '" << cfgname.ToUTF8() << "'\n";
return false;
}
std::list< wxString >::const_iterator sPL = m_Paths.begin();
std::list< wxString >::const_iterator ePL = m_Paths.end();
// the first entry is the current project dir; we never add a project dir
// to the path list in the configuration file
++sPL;
while( sPL != ePL )
{
cfgFile << "\"" << sPL->ToUTF8() << "\"\n";
++sPL;
}
bool bad = cfgFile.bad();
cfgFile.close();
if( bad )
return false;
return true;
}
wxString S3D_FILENAME_RESOLVER::ShortenPath( const wxString& aFullPathName )
{
wxString fname = aFullPathName;
if( m_Paths.empty() )
createPathList();
std::list< wxString >::const_iterator sL = m_Paths.begin();
std::list< wxString >::const_iterator eL = m_Paths.end();
while( sL != eL )
{
wxFileName fpath( *sL, wxT( "" ) );
wxString fps = fpath.GetPathWithSep();
if( std::string::npos != fname.find( fps ) )
{
fname = fname.substr( fps.size() );
#ifdef _WIN32
fname.Replace( wxT( "\\" ), wxT( "/" ) );
#endif
return fname;
}
++sL;
}
return fname;
}
const std::list< wxString >* S3D_FILENAME_RESOLVER::GetPaths( void )
{
return &m_Paths;
}