You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1055 lines
28 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2015-2016 Cirilo Bernardo <cirilo.bernardo@gmail.com>
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, you may find one here:
  18. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  19. * or you may search the http://www.gnu.org website for the version 2 license,
  20. * or you may write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  22. */
  23. #include <fstream>
  24. #include <mutex>
  25. #include <sstream>
  26. #include <wx/filename.h>
  27. #include <wx/log.h>
  28. #include <wx/msgdlg.h>
  29. #include <pgm_base.h>
  30. #include <trace_helpers.h>
  31. #include "common.h"
  32. #include "filename_resolver.h"
  33. // configuration file version
  34. #define CFGFILE_VERSION 1
  35. #define RESOLVER_CONFIG wxT( "3Dresolver.cfg" )
  36. // flag bits used to track different one-off messages to users
  37. #define ERRFLG_ALIAS (1)
  38. #define ERRFLG_RELPATH (2)
  39. #define ERRFLG_ENVPATH (4)
  40. #define MASK_3D_RESOLVER "3D_RESOLVER"
  41. static std::mutex mutex_resolver;
  42. static bool getHollerith( const std::string& aString, size_t& aIndex, wxString& aResult );
  43. FILENAME_RESOLVER::FILENAME_RESOLVER() :
  44. m_pgm( nullptr ),
  45. m_project( nullptr )
  46. {
  47. m_errflags = 0;
  48. }
  49. bool FILENAME_RESOLVER::Set3DConfigDir( const wxString& aConfigDir )
  50. {
  51. if( aConfigDir.empty() )
  52. return false;
  53. wxFileName cfgdir( ExpandEnvVarSubstitutions( aConfigDir, m_project ), "" );
  54. cfgdir.Normalize();
  55. if( !cfgdir.DirExists() )
  56. return false;
  57. m_ConfigDir = cfgdir.GetPath();
  58. createPathList();
  59. return true;
  60. }
  61. bool FILENAME_RESOLVER::SetProject( PROJECT* aProject, bool* flgChanged )
  62. {
  63. m_project = aProject;
  64. if( !aProject )
  65. return false;
  66. wxFileName projdir( ExpandEnvVarSubstitutions( aProject->GetProjectPath(), aProject ), "" );
  67. projdir.Normalize();
  68. if( !projdir.DirExists() )
  69. return false;
  70. m_curProjDir = projdir.GetPath();
  71. if( flgChanged )
  72. *flgChanged = false;
  73. if( m_Paths.empty() )
  74. {
  75. SEARCH_PATH al;
  76. al.m_alias = "${KIPRJMOD}";
  77. al.m_pathvar = "${KIPRJMOD}";
  78. al.m_pathexp = m_curProjDir;
  79. m_Paths.push_back( al );
  80. if( flgChanged )
  81. *flgChanged = true;
  82. }
  83. else
  84. {
  85. if( m_Paths.front().m_pathexp.Cmp( m_curProjDir ) )
  86. {
  87. m_Paths.front().m_pathexp = m_curProjDir;
  88. if( flgChanged )
  89. *flgChanged = true;
  90. }
  91. else
  92. {
  93. return true;
  94. }
  95. }
  96. #ifdef DEBUG
  97. {
  98. std::ostringstream ostr;
  99. ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
  100. ostr << " * [INFO] changed project dir to ";
  101. ostr << m_Paths.front().m_pathexp.ToUTF8();
  102. wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
  103. }
  104. #endif
  105. return true;
  106. }
  107. wxString FILENAME_RESOLVER::GetProjectDir()
  108. {
  109. return m_curProjDir;
  110. }
  111. void FILENAME_RESOLVER::SetProgramBase( PGM_BASE* aBase )
  112. {
  113. m_pgm = aBase;
  114. if( !m_pgm || m_Paths.empty() )
  115. return;
  116. // recreate the path list
  117. m_Paths.clear();
  118. createPathList();
  119. }
  120. bool FILENAME_RESOLVER::createPathList()
  121. {
  122. if( !m_Paths.empty() )
  123. return true;
  124. wxString kmod;
  125. // add an entry for the default search path; at this point
  126. // we cannot set a sensible default so we use an empty string.
  127. // the user may change this later with a call to SetProjectDir()
  128. SEARCH_PATH lpath;
  129. lpath.m_alias = "${KIPRJMOD}";
  130. lpath.m_pathvar = "${KIPRJMOD}";
  131. lpath.m_pathexp = m_curProjDir;
  132. m_Paths.push_back( lpath );
  133. wxFileName fndummy;
  134. wxUniChar psep = fndummy.GetPathSeparator();
  135. std::list< wxString > epaths;
  136. if( GetKicadPaths( epaths ) )
  137. {
  138. for( const wxString& curr_path : epaths )
  139. {
  140. wxString pathVal = ExpandEnvVarSubstitutions( curr_path, m_project );
  141. if( pathVal.empty() )
  142. {
  143. lpath.m_pathexp.clear();
  144. }
  145. else
  146. {
  147. fndummy.Assign( pathVal, "" );
  148. fndummy.Normalize();
  149. lpath.m_pathexp = fndummy.GetFullPath();
  150. }
  151. lpath.m_alias = curr_path;
  152. lpath.m_pathvar = curr_path;
  153. if( !lpath.m_pathexp.empty() && psep == *lpath.m_pathexp.rbegin() )
  154. lpath.m_pathexp.erase( --lpath.m_pathexp.end() );
  155. m_Paths.push_back( lpath );
  156. }
  157. }
  158. if( !m_ConfigDir.empty() )
  159. readPathList();
  160. if( m_Paths.empty() )
  161. return false;
  162. #ifdef DEBUG
  163. wxLogTrace( MASK_3D_RESOLVER, " * [3D model] search paths:\n" );
  164. std::list< SEARCH_PATH >::const_iterator sPL = m_Paths.begin();
  165. while( sPL != m_Paths.end() )
  166. {
  167. wxLogTrace( MASK_3D_RESOLVER, " + %s : '%s'\n", (*sPL).m_alias.GetData(),
  168. (*sPL).m_pathexp.GetData() );
  169. ++sPL;
  170. }
  171. #endif
  172. return true;
  173. }
  174. bool FILENAME_RESOLVER::UpdatePathList( std::vector< SEARCH_PATH >& aPathList )
  175. {
  176. wxUniChar envMarker( '$' );
  177. while( !m_Paths.empty() && envMarker != *m_Paths.back().m_alias.rbegin() )
  178. m_Paths.pop_back();
  179. size_t nI = aPathList.size();
  180. for( size_t i = 0; i < nI; ++i )
  181. addPath( aPathList[i] );
  182. return writePathList();
  183. }
  184. wxString FILENAME_RESOLVER::ResolvePath( const wxString& aFileName )
  185. {
  186. std::lock_guard<std::mutex> lock( mutex_resolver );
  187. if( aFileName.empty() )
  188. return wxEmptyString;
  189. if( m_Paths.empty() )
  190. createPathList();
  191. // first attempt to use the name as specified:
  192. wxString tname = aFileName;
  193. #ifdef _WIN32
  194. // translate from KiCad's internal UNIX-like path to MSWin paths
  195. tname.Replace( wxT( "/" ), wxT( "\\" ) );
  196. #endif
  197. // Note: variable expansion must be performed using a threadsafe
  198. // wrapper for the getenv() system call. If we allow the
  199. // wxFileName::Normalize() routine to perform expansion then
  200. // we will have a race condition since wxWidgets does not assure
  201. // a threadsafe wrapper for getenv().
  202. tname = ExpandEnvVarSubstitutions( tname, m_project );
  203. wxFileName tmpFN( tname );
  204. // in the case of absolute filenames we don't store a map item
  205. if( !aFileName.StartsWith( "${" ) && !aFileName.StartsWith( "$(" )
  206. && !aFileName.StartsWith( ":" ) && tmpFN.IsAbsolute() )
  207. {
  208. tmpFN.Normalize();
  209. if( tmpFN.FileExists() )
  210. return tmpFN.GetFullPath();
  211. return wxEmptyString;
  212. }
  213. // this case covers full paths, leading expanded vars, and paths
  214. // relative to the current working directory (which is not necessarily
  215. // the current project directory)
  216. if( tmpFN.FileExists() )
  217. {
  218. tmpFN.Normalize();
  219. tname = tmpFN.GetFullPath();
  220. // special case: if a path begins with ${ENV_VAR} but is not in the
  221. // resolver's path list then add it.
  222. if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
  223. checkEnvVarPath( aFileName );
  224. return tname;
  225. }
  226. // if a path begins with ${ENV_VAR}/$(ENV_VAR) and is not resolved then the
  227. // file either does not exist or the ENV_VAR is not defined
  228. if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
  229. {
  230. if( !( m_errflags & ERRFLG_ENVPATH ) )
  231. {
  232. m_errflags |= ERRFLG_ENVPATH;
  233. wxString errmsg = "[3D File Resolver] No such path; ensure the environment var is defined";
  234. errmsg.append( "\n" );
  235. errmsg.append( tname );
  236. errmsg.append( "\n" );
  237. wxLogTrace( tracePathsAndFiles, errmsg );
  238. }
  239. return wxEmptyString;
  240. }
  241. // at this point aFileName is:
  242. // a. an aliased shortened name or
  243. // b. cannot be determined
  244. std::list< SEARCH_PATH >::const_iterator sPL = m_Paths.begin();
  245. std::list< SEARCH_PATH >::const_iterator ePL = m_Paths.end();
  246. // check the path relative to the current project directory;
  247. // note: this is not necessarily the same as the current working
  248. // directory, which has already been checked. This case accounts
  249. // for partial paths which do not contain ${KIPRJMOD}.
  250. // This check is performed before checking the path relative to
  251. // ${KISYS3DMOD} so that users can potentially override a model
  252. // within ${KISYS3DMOD}
  253. if( !sPL->m_pathexp.empty() && !tname.StartsWith( ":" ) )
  254. {
  255. tmpFN.Assign( sPL->m_pathexp, "" );
  256. wxString fullPath = tmpFN.GetPathWithSep() + tname;
  257. fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
  258. if( wxFileName::FileExists( fullPath ) )
  259. {
  260. tmpFN.Assign( fullPath );
  261. tmpFN.Normalize();
  262. tname = tmpFN.GetFullPath();
  263. return tname;
  264. }
  265. }
  266. // check the partial path relative to ${KISYS3DMOD} (legacy behavior)
  267. if( !tname.StartsWith( ":" ) )
  268. {
  269. wxFileName fpath;
  270. wxString fullPath( "${KISYS3DMOD}" );
  271. fullPath.Append( fpath.GetPathSeparator() );
  272. fullPath.Append( tname );
  273. fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
  274. fpath.Assign( fullPath );
  275. if( fpath.Normalize() && fpath.FileExists() )
  276. {
  277. tname = fpath.GetFullPath();
  278. return tname;
  279. }
  280. }
  281. // ${ENV_VAR} paths have already been checked; skip them
  282. while( sPL != ePL && ( sPL->m_alias.StartsWith( "${" ) || sPL->m_alias.StartsWith( "$(" ) ) )
  283. ++sPL;
  284. // at this point the filename must contain an alias or else it is invalid
  285. wxString alias; // the alias portion of the short filename
  286. wxString relpath; // the path relative to the alias
  287. if( !SplitAlias( tname, alias, relpath ) )
  288. {
  289. if( !( m_errflags & ERRFLG_RELPATH ) )
  290. {
  291. // this can happen if the file was intended to be relative to
  292. // ${KISYS3DMOD} but ${KISYS3DMOD} not set or incorrect.
  293. m_errflags |= ERRFLG_RELPATH;
  294. wxString errmsg = "[3D File Resolver] No such path";
  295. errmsg.append( "\n" );
  296. errmsg.append( tname );
  297. errmsg.append( "\n" );
  298. wxLogTrace( tracePathsAndFiles, errmsg );
  299. }
  300. return wxEmptyString;
  301. }
  302. while( sPL != ePL )
  303. {
  304. if( !sPL->m_alias.Cmp( alias ) && !sPL->m_pathexp.empty() )
  305. {
  306. wxFileName fpath( wxFileName::DirName( sPL->m_pathexp ) );
  307. wxString fullPath = fpath.GetPathWithSep() + relpath;
  308. fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
  309. if( wxFileName::FileExists( fullPath ) )
  310. {
  311. wxFileName tmp( fullPath );
  312. if( tmp.Normalize() )
  313. tname = tmp.GetFullPath();
  314. return tname;
  315. }
  316. }
  317. ++sPL;
  318. }
  319. if( !( m_errflags & ERRFLG_ALIAS ) )
  320. {
  321. m_errflags |= ERRFLG_ALIAS;
  322. wxString errmsg = "[3D File Resolver] No such path; ensure the path alias is defined";
  323. errmsg.append( "\n" );
  324. errmsg.append( tname.substr( 1 ) );
  325. errmsg.append( "\n" );
  326. wxLogTrace( tracePathsAndFiles, errmsg );
  327. }
  328. return wxEmptyString;
  329. }
  330. bool FILENAME_RESOLVER::addPath( const SEARCH_PATH& aPath )
  331. {
  332. if( aPath.m_alias.empty() || aPath.m_pathvar.empty() )
  333. return false;
  334. std::lock_guard<std::mutex> lock( mutex_resolver );
  335. SEARCH_PATH tpath = aPath;
  336. #ifdef _WIN32
  337. while( tpath.m_pathvar.EndsWith( wxT( "\\" ) ) )
  338. tpath.m_pathvar.erase( tpath.m_pathvar.length() - 1 );
  339. #else
  340. while( tpath.m_pathvar.EndsWith( wxT( "/" ) ) && tpath.m_pathvar.length() > 1 )
  341. tpath.m_pathvar.erase( tpath.m_pathvar.length() - 1 );
  342. #endif
  343. wxFileName path( ExpandEnvVarSubstitutions( tpath.m_pathvar, m_project ), "" );
  344. path.Normalize();
  345. if( !path.DirExists() )
  346. {
  347. // suppress the message if the missing pathvar is the
  348. // legacy KISYS3DMOD variable
  349. if( aPath.m_pathvar.compare( wxT( "${KISYS3DMOD}" ) ) )
  350. {
  351. wxString msg = _( "The given path does not exist" );
  352. msg.append( wxT( "\n" ) );
  353. msg.append( tpath.m_pathvar );
  354. wxMessageBox( msg, _( "3D model search path" ) );
  355. }
  356. tpath.m_pathexp.clear();
  357. }
  358. else
  359. {
  360. tpath.m_pathexp = path.GetFullPath();
  361. #ifdef _WIN32
  362. while( tpath.m_pathexp.EndsWith( wxT( "\\" ) ) )
  363. tpath.m_pathexp.erase( tpath.m_pathexp.length() - 1 );
  364. #else
  365. while( tpath.m_pathexp.EndsWith( wxT( "/" ) ) && tpath.m_pathexp.length() > 1 )
  366. tpath.m_pathexp.erase( tpath.m_pathexp.length() - 1 );
  367. #endif
  368. }
  369. wxString pname = path.GetPath();
  370. std::list< SEARCH_PATH >::iterator sPL = m_Paths.begin();
  371. std::list< SEARCH_PATH >::iterator ePL = m_Paths.end();
  372. while( sPL != ePL )
  373. {
  374. if( !tpath.m_alias.Cmp( sPL->m_alias ) )
  375. {
  376. wxString msg = _( "Alias: " );
  377. msg.append( tpath.m_alias );
  378. msg.append( wxT( "\n" ) );
  379. msg.append( _( "This path: " ) );
  380. msg.append( tpath.m_pathvar );
  381. msg.append( wxT( "\n" ) );
  382. msg.append( _( "Existing path: " ) );
  383. msg.append( sPL->m_pathvar );
  384. wxMessageBox( msg, _( "Bad alias (duplicate name)" ) );
  385. return false;
  386. }
  387. ++sPL;
  388. }
  389. m_Paths.push_back( tpath );
  390. return true;
  391. }
  392. bool FILENAME_RESOLVER::readPathList()
  393. {
  394. if( m_ConfigDir.empty() )
  395. {
  396. std::ostringstream ostr;
  397. ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
  398. wxString errmsg = "3D configuration directory is unknown";
  399. ostr << " * " << errmsg.ToUTF8();
  400. wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
  401. return false;
  402. }
  403. wxFileName cfgpath( m_ConfigDir, RESOLVER_CONFIG );
  404. cfgpath.Normalize();
  405. wxString cfgname = cfgpath.GetFullPath();
  406. size_t nitems = m_Paths.size();
  407. std::ifstream cfgFile;
  408. std::string cfgLine;
  409. if( !wxFileName::Exists( cfgname ) )
  410. {
  411. std::ostringstream ostr;
  412. ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
  413. wxString errmsg = "no 3D configuration file";
  414. ostr << " * " << errmsg.ToUTF8() << " '";
  415. ostr << cfgname.ToUTF8() << "'";
  416. wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
  417. return false;
  418. }
  419. cfgFile.open( cfgname.ToUTF8() );
  420. if( !cfgFile.is_open() )
  421. {
  422. std::ostringstream ostr;
  423. ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
  424. wxString errmsg = "Could not open configuration file";
  425. ostr << " * " << errmsg.ToUTF8() << " '" << cfgname.ToUTF8() << "'";
  426. wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
  427. return false;
  428. }
  429. int lineno = 0;
  430. SEARCH_PATH al;
  431. size_t idx;
  432. int vnum = 0; // version number
  433. while( cfgFile.good() )
  434. {
  435. cfgLine.clear();
  436. std::getline( cfgFile, cfgLine );
  437. ++lineno;
  438. if( cfgLine.empty() )
  439. {
  440. if( cfgFile.eof() )
  441. break;
  442. continue;
  443. }
  444. if( 1 == lineno && cfgLine.compare( 0, 2, "#V" ) == 0 )
  445. {
  446. // extract the version number and parse accordingly
  447. if( cfgLine.size() > 2 )
  448. {
  449. std::istringstream istr;
  450. istr.str( cfgLine.substr( 2 ) );
  451. istr >> vnum;
  452. }
  453. continue;
  454. }
  455. idx = 0;
  456. if( !getHollerith( cfgLine, idx, al.m_alias ) )
  457. continue;
  458. // never add on KISYS3DMOD from a config file
  459. if( !al.m_alias.Cmp( wxT( "KISYS3DMOD" ) ) )
  460. continue;
  461. if( !getHollerith( cfgLine, idx, al.m_pathvar ) )
  462. continue;
  463. if( !getHollerith( cfgLine, idx, al.m_description ) )
  464. continue;
  465. addPath( al );
  466. }
  467. cfgFile.close();
  468. if( vnum < CFGFILE_VERSION )
  469. writePathList();
  470. return( m_Paths.size() != nitems );
  471. }
  472. bool FILENAME_RESOLVER::writePathList()
  473. {
  474. if( m_ConfigDir.empty() )
  475. {
  476. std::ostringstream ostr;
  477. ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
  478. wxString errmsg = _( "3D configuration directory is unknown" );
  479. ostr << " * " << errmsg.ToUTF8();
  480. wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
  481. wxMessageBox( errmsg, _( "Write 3D search path list" ) );
  482. return false;
  483. }
  484. // skip all ${ENV_VAR} alias names
  485. std::list< SEARCH_PATH >::const_iterator sPL = m_Paths.begin();
  486. while( sPL != m_Paths.end() &&
  487. ( sPL->m_alias.StartsWith( "${" ) || sPL->m_alias.StartsWith( "$(" ) ) )
  488. ++sPL;
  489. wxFileName cfgpath( m_ConfigDir, RESOLVER_CONFIG );
  490. wxString cfgname = cfgpath.GetFullPath();
  491. std::ofstream cfgFile;
  492. cfgFile.open( cfgname.ToUTF8(), std::ios_base::trunc );
  493. if( !cfgFile.is_open() )
  494. {
  495. std::ostringstream ostr;
  496. ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
  497. wxString errmsg = _( "Could not open configuration file" );
  498. ostr << " * " << errmsg.ToUTF8() << " '" << cfgname.ToUTF8() << "'";
  499. wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
  500. wxMessageBox( errmsg, _( "Write 3D search path list" ) );
  501. return false;
  502. }
  503. cfgFile << "#V" << CFGFILE_VERSION << "\n";
  504. std::string tstr;
  505. while( sPL != m_Paths.end() )
  506. {
  507. tstr = sPL->m_alias.ToUTF8();
  508. cfgFile << "\"" << tstr.size() << ":" << tstr << "\",";
  509. tstr = sPL->m_pathvar.ToUTF8();
  510. cfgFile << "\"" << tstr.size() << ":" << tstr << "\",";
  511. tstr = sPL->m_description.ToUTF8();
  512. cfgFile << "\"" << tstr.size() << ":" << tstr << "\"\n";
  513. ++sPL;
  514. }
  515. bool bad = cfgFile.bad();
  516. cfgFile.close();
  517. if( bad )
  518. {
  519. wxMessageBox( _( "Problems writing configuration file" ),
  520. _( "Write 3D search path list" ) );
  521. return false;
  522. }
  523. return true;
  524. }
  525. void FILENAME_RESOLVER::checkEnvVarPath( const wxString& aPath )
  526. {
  527. bool useParen = false;
  528. if( aPath.StartsWith( "$(" ) )
  529. useParen = true;
  530. else if( !aPath.StartsWith( "${" ) )
  531. return;
  532. size_t pEnd;
  533. if( useParen )
  534. pEnd = aPath.find( ")" );
  535. else
  536. pEnd = aPath.find( "}" );
  537. if( pEnd == wxString::npos )
  538. return;
  539. wxString envar = aPath.substr( 0, pEnd + 1 );
  540. // check if the alias exists; if not then add it to the end of the
  541. // env var section of the path list
  542. auto sPL = m_Paths.begin();
  543. auto ePL = m_Paths.end();
  544. while( sPL != ePL )
  545. {
  546. if( sPL->m_alias == envar )
  547. return;
  548. if( !sPL->m_alias.StartsWith( "${" ) )
  549. break;
  550. ++sPL;
  551. }
  552. SEARCH_PATH lpath;
  553. lpath.m_alias = envar;
  554. lpath.m_pathvar = lpath.m_alias;
  555. wxFileName tmpFN( ExpandEnvVarSubstitutions( lpath.m_alias, m_project ), "" );
  556. wxUniChar psep = tmpFN.GetPathSeparator();
  557. tmpFN.Normalize();
  558. if( !tmpFN.DirExists() )
  559. return;
  560. lpath.m_pathexp = tmpFN.GetFullPath();
  561. if( !lpath.m_pathexp.empty() && psep == *lpath.m_pathexp.rbegin() )
  562. lpath.m_pathexp.erase( --lpath.m_pathexp.end() );
  563. if( lpath.m_pathexp.empty() )
  564. return;
  565. m_Paths.insert( sPL, lpath );
  566. }
  567. wxString FILENAME_RESOLVER::ShortenPath( const wxString& aFullPathName )
  568. {
  569. wxString fname = aFullPathName;
  570. if( m_Paths.empty() )
  571. createPathList();
  572. std::lock_guard<std::mutex> lock( mutex_resolver );
  573. std::list< SEARCH_PATH >::const_iterator sL = m_Paths.begin();
  574. size_t idx;
  575. while( sL != m_Paths.end() )
  576. {
  577. // undefined paths do not participate in the
  578. // file name shortening procedure
  579. if( sL->m_pathexp.empty() )
  580. {
  581. ++sL;
  582. continue;
  583. }
  584. wxFileName fpath;
  585. // in the case of aliases, ensure that we use the most recent definition
  586. if( sL->m_alias.StartsWith( "${" ) || sL->m_alias.StartsWith( "$(" ) )
  587. {
  588. wxString tpath = ExpandEnvVarSubstitutions( sL->m_alias, m_project );
  589. if( tpath.empty() )
  590. {
  591. ++sL;
  592. continue;
  593. }
  594. fpath.Assign( tpath, wxT( "" ) );
  595. }
  596. else
  597. {
  598. fpath.Assign( sL->m_pathexp, wxT( "" ) );
  599. }
  600. wxString fps = fpath.GetPathWithSep();
  601. wxString tname;
  602. idx = fname.find( fps );
  603. if( idx == 0 )
  604. {
  605. fname = fname.substr( fps.size() );
  606. #ifdef _WIN32
  607. // ensure only the '/' separator is used in the internal name
  608. fname.Replace( wxT( "\\" ), wxT( "/" ) );
  609. #endif
  610. if( sL->m_alias.StartsWith( "${" ) || sL->m_alias.StartsWith( "$(" ) )
  611. {
  612. // old style ENV_VAR
  613. tname = sL->m_alias;
  614. tname.Append( "/" );
  615. tname.append( fname );
  616. }
  617. else
  618. {
  619. // new style alias
  620. tname = ":";
  621. tname.append( sL->m_alias );
  622. tname.append( ":" );
  623. tname.append( fname );
  624. }
  625. return tname;
  626. }
  627. ++sL;
  628. }
  629. #ifdef _WIN32
  630. // it is strange to convert an MSWin full path to use the
  631. // UNIX separator but this is done for consistency and can
  632. // be helpful even when transferring project files from
  633. // MSWin to *NIX.
  634. fname.Replace( wxT( "\\" ), wxT( "/" ) );
  635. #endif
  636. return fname;
  637. }
  638. const std::list< SEARCH_PATH >* FILENAME_RESOLVER::GetPaths()
  639. {
  640. return &m_Paths;
  641. }
  642. bool FILENAME_RESOLVER::SplitAlias( const wxString& aFileName,
  643. wxString& anAlias, wxString& aRelPath )
  644. {
  645. anAlias.clear();
  646. aRelPath.clear();
  647. if( !aFileName.StartsWith( wxT( ":" ) ) )
  648. return false;
  649. size_t tagpos = aFileName.find( wxT( ":" ), 1 );
  650. if( wxString::npos == tagpos || 1 == tagpos )
  651. return false;
  652. if( tagpos + 1 >= aFileName.length() )
  653. return false;
  654. anAlias = aFileName.substr( 1, tagpos - 1 );
  655. aRelPath = aFileName.substr( tagpos + 1 );
  656. return true;
  657. }
  658. static bool getHollerith( const std::string& aString, size_t& aIndex, wxString& aResult )
  659. {
  660. aResult.clear();
  661. if( aIndex >= aString.size() )
  662. {
  663. std::ostringstream ostr;
  664. ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
  665. wxString errmsg = "bad Hollerith string on line";
  666. ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
  667. wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
  668. return false;
  669. }
  670. size_t i2 = aString.find( '"', aIndex );
  671. if( std::string::npos == i2 )
  672. {
  673. std::ostringstream ostr;
  674. ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
  675. wxString errmsg = "missing opening quote mark in config file";
  676. ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
  677. wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
  678. return false;
  679. }
  680. ++i2;
  681. if( i2 >= aString.size() )
  682. {
  683. std::ostringstream ostr;
  684. ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
  685. wxString errmsg = "invalid entry (unexpected end of line)";
  686. ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
  687. wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
  688. return false;
  689. }
  690. std::string tnum;
  691. while( aString[i2] >= '0' && aString[i2] <= '9' )
  692. tnum.append( 1, aString[i2++] );
  693. if( tnum.empty() || aString[i2++] != ':' )
  694. {
  695. std::ostringstream ostr;
  696. ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
  697. wxString errmsg = "bad Hollerith string on line";
  698. ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
  699. wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
  700. return false;
  701. }
  702. std::istringstream istr;
  703. istr.str( tnum );
  704. size_t nchars;
  705. istr >> nchars;
  706. if( (i2 + nchars) >= aString.size() )
  707. {
  708. std::ostringstream ostr;
  709. ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
  710. wxString errmsg = "invalid entry (unexpected end of line)";
  711. ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
  712. wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
  713. return false;
  714. }
  715. if( nchars > 0 )
  716. {
  717. aResult = wxString::FromUTF8( aString.substr( i2, nchars ).c_str() );
  718. i2 += nchars;
  719. }
  720. if( aString[i2] != '"' )
  721. {
  722. std::ostringstream ostr;
  723. ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
  724. wxString errmsg = "missing closing quote mark in config file";
  725. ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
  726. wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
  727. return false;
  728. }
  729. aIndex = i2 + 1;
  730. return true;
  731. }
  732. bool FILENAME_RESOLVER::ValidateFileName( const wxString& aFileName, bool& hasAlias )
  733. {
  734. // Rules:
  735. // 1. The generic form of an aliased 3D relative path is:
  736. // ALIAS:relative/path
  737. // 2. ALIAS is a UTF string excluding wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" )
  738. // 3. The relative path must be a valid relative path for the platform
  739. hasAlias = false;
  740. if( aFileName.empty() )
  741. return false;
  742. wxString filename = aFileName;
  743. size_t pos0 = aFileName.find( ':' );
  744. // ensure that the file separators suit the current platform
  745. #ifdef __WINDOWS__
  746. filename.Replace( wxT( "/" ), wxT( "\\" ) );
  747. // if we see the :\ pattern then it must be a drive designator
  748. if( pos0 != wxString::npos )
  749. {
  750. size_t pos1 = filename.find( wxT( ":\\" ) );
  751. if( pos1 != wxString::npos && ( pos1 != pos0 || pos1 != 1 ) )
  752. return false;
  753. // if we have a drive designator then we have no alias
  754. if( pos1 != wxString::npos )
  755. pos0 = wxString::npos;
  756. }
  757. #else
  758. filename.Replace( wxT( "\\" ), wxT( "/" ) );
  759. #endif
  760. // names may not end with ':'
  761. if( pos0 == aFileName.length() -1 )
  762. return false;
  763. if( pos0 != wxString::npos )
  764. {
  765. // ensure the alias component is not empty
  766. if( pos0 == 0 )
  767. return false;
  768. wxString lpath = filename.substr( 0, pos0 );
  769. // check the alias for restricted characters
  770. if( wxString::npos != lpath.find_first_of( wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" ) ) )
  771. return false;
  772. hasAlias = true;
  773. }
  774. return true;
  775. }
  776. bool FILENAME_RESOLVER::GetKicadPaths( std::list< wxString >& paths )
  777. {
  778. paths.clear();
  779. if( !m_pgm )
  780. return false;
  781. bool hasKisys3D = false;
  782. // iterate over the list of internally defined ENV VARs
  783. // and add them to the paths list
  784. ENV_VAR_MAP_CITER mS = m_pgm->GetLocalEnvVariables().begin();
  785. ENV_VAR_MAP_CITER mE = m_pgm->GetLocalEnvVariables().end();
  786. while( mS != mE )
  787. {
  788. // filter out URLs, template directories, and known system paths
  789. if( mS->first == wxString( "KICAD_PTEMPLATES" )
  790. || mS->first == wxString( "KIGITHUB" )
  791. || mS->first == wxString( "KISYSMOD" ) )
  792. {
  793. ++mS;
  794. continue;
  795. }
  796. if( wxString::npos != mS->second.GetValue().find( wxString( "://" ) ) )
  797. {
  798. ++mS;
  799. continue;
  800. }
  801. wxString tmp( "${" );
  802. tmp.Append( mS->first );
  803. tmp.Append( "}" );
  804. paths.push_back( tmp );
  805. if( tmp == "${KISYS3DMOD}" )
  806. hasKisys3D = true;
  807. ++mS;
  808. }
  809. if( !hasKisys3D )
  810. paths.emplace_back("${KISYS3DMOD}" );
  811. return true;
  812. }