KiCad PCB EDA Suite
filename_resolver.cpp
Go to the documentation of this file.
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 
24 #include <fstream>
25 #include <mutex>
26 #include <sstream>
27 
28 #include <wx/filename.h>
29 #include <wx/log.h>
30 #include <wx/msgdlg.h>
31 #include <pgm_base.h>
32 #include <trace_helpers.h>
33 
34 #include "common.h"
35 #include "filename_resolver.h"
36 
37 // configuration file version
38 #define CFGFILE_VERSION 1
39 #define RESOLVER_CONFIG wxT( "3Dresolver.cfg" )
40 
41 // flag bits used to track different one-off messages to users
42 #define ERRFLG_ALIAS (1)
43 #define ERRFLG_RELPATH (2)
44 #define ERRFLG_ENVPATH (4)
45 
46 #define MASK_3D_RESOLVER "3D_RESOLVER"
47 
48 static std::mutex mutex_resolver;
49 
50 static bool getHollerith( const std::string& aString, size_t& aIndex, wxString& aResult );
51 
52 
54  m_pgm( nullptr ),
55  m_project( nullptr )
56 {
57  m_errflags = 0;
58 }
59 
60 
61 bool FILENAME_RESOLVER::Set3DConfigDir( const wxString& aConfigDir )
62 {
63  if( aConfigDir.empty() )
64  return false;
65 
66  wxFileName cfgdir( ExpandEnvVarSubstitutions( aConfigDir, m_project ), "" );
67 
68  cfgdir.Normalize();
69 
70  if( !cfgdir.DirExists() )
71  return false;
72 
73  m_configDir = cfgdir.GetPath();
75 
76  return true;
77 }
78 
79 
80 bool FILENAME_RESOLVER::SetProject( PROJECT* aProject, bool* flgChanged )
81 {
82  m_project = aProject;
83 
84  if( !aProject )
85  return false;
86 
87  wxFileName projdir( ExpandEnvVarSubstitutions( aProject->GetProjectPath(), aProject ), "" );
88 
89  projdir.Normalize();
90 
91  if( !projdir.DirExists() )
92  return false;
93 
94  m_curProjDir = projdir.GetPath();
95 
96  if( flgChanged )
97  *flgChanged = false;
98 
99  if( m_paths.empty() )
100  {
101  SEARCH_PATH al;
102  al.m_Alias = "${KIPRJMOD}";
103  al.m_Pathvar = "${KIPRJMOD}";
104  al.m_Pathexp = m_curProjDir;
105  m_paths.push_back( al );
106 
107  if( flgChanged )
108  *flgChanged = true;
109  }
110  else
111  {
112  if( m_paths.front().m_Pathexp.Cmp( m_curProjDir ) )
113  {
114  m_paths.front().m_Pathexp = m_curProjDir;
115 
116  if( flgChanged )
117  *flgChanged = true;
118  }
119  else
120  {
121  return true;
122  }
123  }
124 
125 #ifdef DEBUG
126  {
127  std::ostringstream ostr;
128  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
129  ostr << " * [INFO] changed project dir to ";
130  ostr << m_paths.front().m_Pathexp.ToUTF8();
131  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
132  }
133 #endif
134 
135  return true;
136 }
137 
138 
140 {
141  return m_curProjDir;
142 }
143 
144 
146 {
147  m_pgm = aBase;
148 
149  if( !m_pgm || m_paths.empty() )
150  return;
151 
152  // recreate the path list
153  m_paths.clear();
154  createPathList();
155 }
156 
157 
159 {
160  if( !m_paths.empty() )
161  return true;
162 
163  wxString kmod;
164 
165  // add an entry for the default search path; at this point
166  // we cannot set a sensible default so we use an empty string.
167  // the user may change this later with a call to SetProjectDir()
168 
169  SEARCH_PATH lpath;
170  lpath.m_Alias = "${KIPRJMOD}";
171  lpath.m_Pathvar = "${KIPRJMOD}";
172  lpath.m_Pathexp = m_curProjDir;
173  m_paths.push_back( lpath );
174  wxFileName fndummy;
175  wxUniChar psep = fndummy.GetPathSeparator();
176  std::list< wxString > epaths;
177 
178  if( GetKicadPaths( epaths ) )
179  {
180  for( const wxString& curr_path : epaths )
181  {
182  wxString pathVal = ExpandEnvVarSubstitutions( curr_path, m_project );
183 
184  if( pathVal.empty() )
185  {
186  lpath.m_Pathexp.clear();
187  }
188  else
189  {
190  fndummy.Assign( pathVal, "" );
191  fndummy.Normalize();
192  lpath.m_Pathexp = fndummy.GetFullPath();
193  }
194 
195  lpath.m_Alias = curr_path;
196  lpath.m_Pathvar = curr_path;
197 
198  if( !lpath.m_Pathexp.empty() && psep == *lpath.m_Pathexp.rbegin() )
199  lpath.m_Pathexp.erase( --lpath.m_Pathexp.end() );
200 
201  m_paths.push_back( lpath );
202  }
203  }
204 
205  if( !m_configDir.empty() )
206  readPathList();
207 
208  if( m_paths.empty() )
209  return false;
210 
211 #ifdef DEBUG
212  wxLogTrace( MASK_3D_RESOLVER, " * [3D model] search paths:\n" );
213  std::list< SEARCH_PATH >::const_iterator sPL = m_paths.begin();
214 
215  while( sPL != m_paths.end() )
216  {
217  wxLogTrace( MASK_3D_RESOLVER, " + %s : '%s'\n", (*sPL).m_Alias.GetData(),
218  (*sPL).m_Pathexp.GetData() );
219  ++sPL;
220  }
221 #endif
222 
223  return true;
224 }
225 
226 
227 bool FILENAME_RESOLVER::UpdatePathList( std::vector< SEARCH_PATH >& aPathList )
228 {
229  wxUniChar envMarker( '$' );
230 
231  while( !m_paths.empty() && envMarker != *m_paths.back().m_Alias.rbegin() )
232  m_paths.pop_back();
233 
234  size_t nI = aPathList.size();
235 
236  for( size_t i = 0; i < nI; ++i )
237  addPath( aPathList[i] );
238 
239  return writePathList();
240 }
241 
242 
243 wxString FILENAME_RESOLVER::ResolvePath( const wxString& aFileName )
244 {
245  std::lock_guard<std::mutex> lock( mutex_resolver );
246 
247  if( aFileName.empty() )
248  return wxEmptyString;
249 
250  if( m_paths.empty() )
251  createPathList();
252 
253  // first attempt to use the name as specified:
254  wxString tname = aFileName;
255 
256  #ifdef _WIN32
257  // translate from KiCad's internal UNIX-like path to MSWin paths
258  tname.Replace( wxT( "/" ), wxT( "\\" ) );
259  #endif
260 
261  // Note: variable expansion must be performed using a threadsafe
262  // wrapper for the getenv() system call. If we allow the
263  // wxFileName::Normalize() routine to perform expansion then
264  // we will have a race condition since wxWidgets does not assure
265  // a threadsafe wrapper for getenv().
266  tname = ExpandEnvVarSubstitutions( tname, m_project );
267 
268  wxFileName tmpFN( tname );
269 
270  // in the case of absolute filenames we don't store a map item
271  if( !aFileName.StartsWith( "${" ) && !aFileName.StartsWith( "$(" )
272  && !aFileName.StartsWith( ":" ) && tmpFN.IsAbsolute() )
273  {
274  tmpFN.Normalize();
275 
276  if( tmpFN.FileExists() )
277  return tmpFN.GetFullPath();
278 
279  return wxEmptyString;
280  }
281 
282  // this case covers full paths, leading expanded vars, and paths
283  // relative to the current working directory (which is not necessarily
284  // the current project directory)
285  if( tmpFN.FileExists() )
286  {
287  tmpFN.Normalize();
288  tname = tmpFN.GetFullPath();
289 
290  // special case: if a path begins with ${ENV_VAR} but is not in the
291  // resolver's path list then add it.
292  if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
293  checkEnvVarPath( aFileName );
294 
295  return tname;
296  }
297 
298  // if a path begins with ${ENV_VAR}/$(ENV_VAR) and is not resolved then the
299  // file either does not exist or the ENV_VAR is not defined
300  if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
301  {
302  if( !( m_errflags & ERRFLG_ENVPATH ) )
303  {
305  wxString errmsg = "[3D File Resolver] No such path; ensure the environment var is defined";
306  errmsg.append( "\n" );
307  errmsg.append( tname );
308  errmsg.append( "\n" );
309  wxLogTrace( tracePathsAndFiles, errmsg );
310  }
311 
312  return wxEmptyString;
313  }
314 
315  // at this point aFileName is:
316  // a. an aliased shortened name or
317  // b. cannot be determined
318 
319  std::list< SEARCH_PATH >::const_iterator sPL = m_paths.begin();
320  std::list< SEARCH_PATH >::const_iterator ePL = m_paths.end();
321 
322  // check the path relative to the current project directory;
323  // note: this is not necessarily the same as the current working
324  // directory, which has already been checked. This case accounts
325  // for partial paths which do not contain ${KIPRJMOD}.
326  // This check is performed before checking the path relative to
327  // ${KISYS3DMOD} so that users can potentially override a model
328  // within ${KISYS3DMOD}
329  if( !sPL->m_Pathexp.empty() && !tname.StartsWith( ":" ) )
330  {
331  tmpFN.Assign( sPL->m_Pathexp, "" );
332  wxString fullPath = tmpFN.GetPathWithSep() + tname;
333 
334  fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
335 
336  if( wxFileName::FileExists( fullPath ) )
337  {
338  tmpFN.Assign( fullPath );
339  tmpFN.Normalize();
340  tname = tmpFN.GetFullPath();
341  return tname;
342  }
343 
344  }
345 
346  // check the partial path relative to ${KISYS3DMOD} (legacy behavior)
347  if( !tname.StartsWith( ":" ) )
348  {
349  wxFileName fpath;
350  wxString fullPath( "${KISYS3DMOD}" );
351  fullPath.Append( fpath.GetPathSeparator() );
352  fullPath.Append( tname );
353  fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
354  fpath.Assign( fullPath );
355 
356  if( fpath.Normalize() && fpath.FileExists() )
357  {
358  tname = fpath.GetFullPath();
359  return tname;
360  }
361 
362  }
363 
364  // ${ENV_VAR} paths have already been checked; skip them
365  while( sPL != ePL && ( sPL->m_Alias.StartsWith( "${" ) || sPL->m_Alias.StartsWith( "$(" ) ) )
366  ++sPL;
367 
368  // at this point the filename must contain an alias or else it is invalid
369  wxString alias; // the alias portion of the short filename
370  wxString relpath; // the path relative to the alias
371 
372  if( !SplitAlias( tname, alias, relpath ) )
373  {
374  if( !( m_errflags & ERRFLG_RELPATH ) )
375  {
376  // this can happen if the file was intended to be relative to
377  // ${KISYS3DMOD} but ${KISYS3DMOD} not set or incorrect.
379  wxString errmsg = "[3D File Resolver] No such path";
380  errmsg.append( "\n" );
381  errmsg.append( tname );
382  errmsg.append( "\n" );
383  wxLogTrace( tracePathsAndFiles, errmsg );
384  }
385 
386  return wxEmptyString;
387  }
388 
389  while( sPL != ePL )
390  {
391  if( !sPL->m_Alias.Cmp( alias ) && !sPL->m_Pathexp.empty() )
392  {
393  wxFileName fpath( wxFileName::DirName( sPL->m_Pathexp ) );
394  wxString fullPath = fpath.GetPathWithSep() + relpath;
395 
396  fullPath = ExpandEnvVarSubstitutions( fullPath, m_project );
397 
398  if( wxFileName::FileExists( fullPath ) )
399  {
400  wxFileName tmp( fullPath );
401 
402  if( tmp.Normalize() )
403  tname = tmp.GetFullPath();
404 
405  return tname;
406  }
407  }
408 
409  ++sPL;
410  }
411 
412  if( !( m_errflags & ERRFLG_ALIAS ) )
413  {
415  wxString errmsg = "[3D File Resolver] No such path; ensure the path alias is defined";
416  errmsg.append( "\n" );
417  errmsg.append( tname.substr( 1 ) );
418  errmsg.append( "\n" );
419  wxLogTrace( tracePathsAndFiles, errmsg );
420  }
421 
422  return wxEmptyString;
423 }
424 
425 
427 {
428  if( aPath.m_Alias.empty() || aPath.m_Pathvar.empty() )
429  return false;
430 
431  std::lock_guard<std::mutex> lock( mutex_resolver );
432 
433  SEARCH_PATH tpath = aPath;
434 
435  #ifdef _WIN32
436  while( tpath.m_Pathvar.EndsWith( wxT( "\\" ) ) )
437  tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
438  #else
439  while( tpath.m_Pathvar.EndsWith( wxT( "/" ) ) && tpath.m_Pathvar.length() > 1 )
440  tpath.m_Pathvar.erase( tpath.m_Pathvar.length() - 1 );
441  #endif
442 
443  wxFileName path( ExpandEnvVarSubstitutions( tpath.m_Pathvar, m_project ), "" );
444 
445  path.Normalize();
446 
447  if( !path.DirExists() )
448  {
449  // suppress the message if the missing pathvar is the
450  // legacy KISYS3DMOD variable
451  if( aPath.m_Pathvar.compare( wxT( "${KISYS3DMOD}" ) ) )
452  {
453  wxString msg = _( "The given path does not exist" );
454  msg.append( wxT( "\n" ) );
455  msg.append( tpath.m_Pathvar );
456  wxMessageBox( msg, _( "3D model search path" ) );
457  }
458 
459  tpath.m_Pathexp.clear();
460  }
461  else
462  {
463  tpath.m_Pathexp = path.GetFullPath();
464 
465 #ifdef _WIN32
466  while( tpath.m_Pathexp.EndsWith( wxT( "\\" ) ) )
467  tpath.m_Pathexp.erase( tpath.m_Pathexp.length() - 1 );
468 #else
469  while( tpath.m_Pathexp.EndsWith( wxT( "/" ) ) && tpath.m_Pathexp.length() > 1 )
470  tpath.m_Pathexp.erase( tpath.m_Pathexp.length() - 1 );
471 #endif
472  }
473 
474  wxString pname = path.GetPath();
475  std::list< SEARCH_PATH >::iterator sPL = m_paths.begin();
476  std::list< SEARCH_PATH >::iterator ePL = m_paths.end();
477 
478  while( sPL != ePL )
479  {
480  if( !tpath.m_Alias.Cmp( sPL->m_Alias ) )
481  {
482  wxString msg = _( "Alias: " );
483  msg.append( tpath.m_Alias );
484  msg.append( wxT( "\n" ) );
485  msg.append( _( "This path:" ) + wxS( " " ) );
486  msg.append( tpath.m_Pathvar );
487  msg.append( wxT( "\n" ) );
488  msg.append( _( "Existing path:" ) + wxS( " " ) );
489  msg.append( sPL->m_Pathvar );
490  wxMessageBox( msg, _( "Bad alias (duplicate name)" ) );
491 
492  return false;
493  }
494 
495  ++sPL;
496  }
497 
498  m_paths.push_back( tpath );
499  return true;
500 }
501 
502 
504 {
505  if( m_configDir.empty() )
506  {
507  std::ostringstream ostr;
508  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
509  wxString errmsg = "3D configuration directory is unknown";
510  ostr << " * " << errmsg.ToUTF8();
511  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
512  return false;
513  }
514 
515  wxFileName cfgpath( m_configDir, RESOLVER_CONFIG );
516  cfgpath.Normalize();
517  wxString cfgname = cfgpath.GetFullPath();
518 
519  size_t nitems = m_paths.size();
520 
521  std::ifstream cfgFile;
522  std::string cfgLine;
523 
524  if( !wxFileName::Exists( cfgname ) )
525  {
526  std::ostringstream ostr;
527  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
528  wxString errmsg = "no 3D configuration file";
529  ostr << " * " << errmsg.ToUTF8() << " '";
530  ostr << cfgname.ToUTF8() << "'";
531  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
532  return false;
533  }
534 
535  cfgFile.open( cfgname.ToUTF8() );
536 
537  if( !cfgFile.is_open() )
538  {
539  std::ostringstream ostr;
540  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
541  wxString errmsg = "Could not open configuration file";
542  ostr << " * " << errmsg.ToUTF8() << " '" << cfgname.ToUTF8() << "'";
543  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
544  return false;
545  }
546 
547  int lineno = 0;
548  SEARCH_PATH al;
549  size_t idx;
550  int vnum = 0; // version number
551 
552  while( cfgFile.good() )
553  {
554  cfgLine.clear();
555  std::getline( cfgFile, cfgLine );
556  ++lineno;
557 
558  if( cfgLine.empty() )
559  {
560  if( cfgFile.eof() )
561  break;
562 
563  continue;
564  }
565 
566  if( 1 == lineno && cfgLine.compare( 0, 2, "#V" ) == 0 )
567  {
568  // extract the version number and parse accordingly
569  if( cfgLine.size() > 2 )
570  {
571  std::istringstream istr;
572  istr.str( cfgLine.substr( 2 ) );
573  istr >> vnum;
574  }
575 
576  continue;
577  }
578 
579  idx = 0;
580 
581  if( !getHollerith( cfgLine, idx, al.m_Alias ) )
582  continue;
583 
584  // never add on KISYS3DMOD from a config file
585  if( !al.m_Alias.Cmp( wxT( "KISYS3DMOD" ) ) )
586  continue;
587 
588  if( !getHollerith( cfgLine, idx, al.m_Pathvar ) )
589  continue;
590 
591  if( !getHollerith( cfgLine, idx, al.m_Description ) )
592  continue;
593 
594  addPath( al );
595  }
596 
597  cfgFile.close();
598 
599  if( vnum < CFGFILE_VERSION )
600  writePathList();
601 
602  return( m_paths.size() != nitems );
603 }
604 
605 
607 {
608  if( m_configDir.empty() )
609  {
610  std::ostringstream ostr;
611  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
612  wxString errmsg = _( "3D configuration directory is unknown" );
613  ostr << " * " << errmsg.ToUTF8();
614  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
615  wxMessageBox( errmsg, _( "Write 3D search path list" ) );
616 
617  return false;
618  }
619 
620  // skip all ${ENV_VAR} alias names
621  std::list< SEARCH_PATH >::const_iterator sPL = m_paths.begin();
622 
623  while( sPL != m_paths.end() &&
624  ( sPL->m_Alias.StartsWith( "${" ) || sPL->m_Alias.StartsWith( "$(" ) ) )
625  ++sPL;
626 
627  wxFileName cfgpath( m_configDir, RESOLVER_CONFIG );
628  wxString cfgname = cfgpath.GetFullPath();
629  std::ofstream cfgFile;
630 
631  cfgFile.open( cfgname.ToUTF8(), std::ios_base::trunc );
632 
633  if( !cfgFile.is_open() )
634  {
635  std::ostringstream ostr;
636  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
637  wxString errmsg = _( "Could not open configuration file" );
638  ostr << " * " << errmsg.ToUTF8() << " '" << cfgname.ToUTF8() << "'";
639  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
640  wxMessageBox( errmsg, _( "Write 3D search path list" ) );
641 
642  return false;
643  }
644 
645  cfgFile << "#V" << CFGFILE_VERSION << "\n";
646  std::string tstr;
647 
648  while( sPL != m_paths.end() )
649  {
650  tstr = sPL->m_Alias.ToUTF8();
651  cfgFile << "\"" << tstr.size() << ":" << tstr << "\",";
652  tstr = sPL->m_Pathvar.ToUTF8();
653  cfgFile << "\"" << tstr.size() << ":" << tstr << "\",";
654  tstr = sPL->m_Description.ToUTF8();
655  cfgFile << "\"" << tstr.size() << ":" << tstr << "\"\n";
656  ++sPL;
657  }
658 
659  bool bad = cfgFile.bad();
660  cfgFile.close();
661 
662  if( bad )
663  {
664  wxMessageBox( _( "Problems writing configuration file" ),
665  _( "Write 3D search path list" ) );
666 
667  return false;
668  }
669 
670  return true;
671 }
672 
673 
674 void FILENAME_RESOLVER::checkEnvVarPath( const wxString& aPath )
675 {
676  bool useParen = false;
677 
678  if( aPath.StartsWith( "$(" ) )
679  useParen = true;
680  else if( !aPath.StartsWith( "${" ) )
681  return;
682 
683  size_t pEnd;
684 
685  if( useParen )
686  pEnd = aPath.find( ")" );
687  else
688  pEnd = aPath.find( "}" );
689 
690  if( pEnd == wxString::npos )
691  return;
692 
693  wxString envar = aPath.substr( 0, pEnd + 1 );
694 
695  // check if the alias exists; if not then add it to the end of the
696  // env var section of the path list
697  auto sPL = m_paths.begin();
698  auto ePL = m_paths.end();
699 
700  while( sPL != ePL )
701  {
702  if( sPL->m_Alias == envar )
703  return;
704 
705  if( !sPL->m_Alias.StartsWith( "${" ) )
706  break;
707 
708  ++sPL;
709  }
710 
711  SEARCH_PATH lpath;
712  lpath.m_Alias = envar;
713  lpath.m_Pathvar = lpath.m_Alias;
714  wxFileName tmpFN( ExpandEnvVarSubstitutions( lpath.m_Alias, m_project ), "" );
715 
716  wxUniChar psep = tmpFN.GetPathSeparator();
717  tmpFN.Normalize();
718 
719  if( !tmpFN.DirExists() )
720  return;
721 
722  lpath.m_Pathexp = tmpFN.GetFullPath();
723 
724  if( !lpath.m_Pathexp.empty() && psep == *lpath.m_Pathexp.rbegin() )
725  lpath.m_Pathexp.erase( --lpath.m_Pathexp.end() );
726 
727  if( lpath.m_Pathexp.empty() )
728  return;
729 
730  m_paths.insert( sPL, lpath );
731 }
732 
733 
734 wxString FILENAME_RESOLVER::ShortenPath( const wxString& aFullPathName )
735 {
736  wxString fname = aFullPathName;
737 
738  if( m_paths.empty() )
739  createPathList();
740 
741  std::lock_guard<std::mutex> lock( mutex_resolver );
742 
743  std::list< SEARCH_PATH >::const_iterator sL = m_paths.begin();
744  size_t idx;
745 
746  while( sL != m_paths.end() )
747  {
748  // undefined paths do not participate in the
749  // file name shortening procedure
750  if( sL->m_Pathexp.empty() )
751  {
752  ++sL;
753  continue;
754  }
755 
756  wxFileName fpath;
757 
758  // in the case of aliases, ensure that we use the most recent definition
759  if( sL->m_Alias.StartsWith( "${" ) || sL->m_Alias.StartsWith( "$(" ) )
760  {
761  wxString tpath = ExpandEnvVarSubstitutions( sL->m_Alias, m_project );
762 
763  if( tpath.empty() )
764  {
765  ++sL;
766  continue;
767  }
768 
769  fpath.Assign( tpath, wxT( "" ) );
770  }
771  else
772  {
773  fpath.Assign( sL->m_Pathexp, wxT( "" ) );
774  }
775 
776  wxString fps = fpath.GetPathWithSep();
777  wxString tname;
778 
779  idx = fname.find( fps );
780 
781  if( idx == 0 )
782  {
783  fname = fname.substr( fps.size() );
784 
785  #ifdef _WIN32
786  // ensure only the '/' separator is used in the internal name
787  fname.Replace( wxT( "\\" ), wxT( "/" ) );
788  #endif
789 
790  if( sL->m_Alias.StartsWith( "${" ) || sL->m_Alias.StartsWith( "$(" ) )
791  {
792  // old style ENV_VAR
793  tname = sL->m_Alias;
794  tname.Append( "/" );
795  tname.append( fname );
796  }
797  else
798  {
799  // new style alias
800  tname = ":";
801  tname.append( sL->m_Alias );
802  tname.append( ":" );
803  tname.append( fname );
804  }
805 
806  return tname;
807  }
808 
809  ++sL;
810  }
811 
812 #ifdef _WIN32
813  // it is strange to convert an MSWin full path to use the
814  // UNIX separator but this is done for consistency and can
815  // be helpful even when transferring project files from
816  // MSWin to *NIX.
817  fname.Replace( wxT( "\\" ), wxT( "/" ) );
818 #endif
819 
820  return fname;
821 }
822 
823 
824 
825 const std::list< SEARCH_PATH >* FILENAME_RESOLVER::GetPaths()
826 {
827  return &m_paths;
828 }
829 
830 
831 bool FILENAME_RESOLVER::SplitAlias( const wxString& aFileName,
832  wxString& anAlias, wxString& aRelPath )
833 {
834  anAlias.clear();
835  aRelPath.clear();
836 
837  if( !aFileName.StartsWith( wxT( ":" ) ) )
838  return false;
839 
840  size_t tagpos = aFileName.find( wxT( ":" ), 1 );
841 
842  if( wxString::npos == tagpos || 1 == tagpos )
843  return false;
844 
845  if( tagpos + 1 >= aFileName.length() )
846  return false;
847 
848  anAlias = aFileName.substr( 1, tagpos - 1 );
849  aRelPath = aFileName.substr( tagpos + 1 );
850 
851  return true;
852 }
853 
854 
855 static bool getHollerith( const std::string& aString, size_t& aIndex, wxString& aResult )
856 {
857  aResult.clear();
858 
859  if( aIndex >= aString.size() )
860  {
861  std::ostringstream ostr;
862  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
863  wxString errmsg = "bad Hollerith string on line";
864  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
865  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
866 
867  return false;
868  }
869 
870  size_t i2 = aString.find( '"', aIndex );
871 
872  if( std::string::npos == i2 )
873  {
874  std::ostringstream ostr;
875  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
876  wxString errmsg = "missing opening quote mark in config file";
877  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
878  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
879 
880  return false;
881  }
882 
883  ++i2;
884 
885  if( i2 >= aString.size() )
886  {
887  std::ostringstream ostr;
888  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
889  wxString errmsg = "invalid entry (unexpected end of line)";
890  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
891  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
892 
893  return false;
894  }
895 
896  std::string tnum;
897 
898  while( aString[i2] >= '0' && aString[i2] <= '9' )
899  tnum.append( 1, aString[i2++] );
900 
901  if( tnum.empty() || aString[i2++] != ':' )
902  {
903  std::ostringstream ostr;
904  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
905  wxString errmsg = "bad Hollerith string on line";
906  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
907  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
908 
909  return false;
910  }
911 
912  std::istringstream istr;
913  istr.str( tnum );
914  size_t nchars;
915  istr >> nchars;
916 
917  if( (i2 + nchars) >= aString.size() )
918  {
919  std::ostringstream ostr;
920  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
921  wxString errmsg = "invalid entry (unexpected end of line)";
922  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
923  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
924 
925  return false;
926  }
927 
928  if( nchars > 0 )
929  {
930  aResult = wxString::FromUTF8( aString.substr( i2, nchars ).c_str() );
931  i2 += nchars;
932  }
933 
934  if( aString[i2] != '"' )
935  {
936  std::ostringstream ostr;
937  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
938  wxString errmsg = "missing closing quote mark in config file";
939  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
940  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
941 
942  return false;
943  }
944 
945  aIndex = i2 + 1;
946  return true;
947 }
948 
949 
950 bool FILENAME_RESOLVER::ValidateFileName( const wxString& aFileName, bool& hasAlias )
951 {
952  // Rules:
953  // 1. The generic form of an aliased 3D relative path is:
954  // ALIAS:relative/path
955  // 2. ALIAS is a UTF string excluding wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" )
956  // 3. The relative path must be a valid relative path for the platform
957  hasAlias = false;
958 
959  if( aFileName.empty() )
960  return false;
961 
962  wxString filename = aFileName;
963  size_t pos0 = aFileName.find( ':' );
964 
965  // ensure that the file separators suit the current platform
966  #ifdef __WINDOWS__
967  filename.Replace( wxT( "/" ), wxT( "\\" ) );
968 
969  // if we see the :\ pattern then it must be a drive designator
970  if( pos0 != wxString::npos )
971  {
972  size_t pos1 = filename.find( wxT( ":\\" ) );
973 
974  if( pos1 != wxString::npos && ( pos1 != pos0 || pos1 != 1 ) )
975  return false;
976 
977  // if we have a drive designator then we have no alias
978  if( pos1 != wxString::npos )
979  pos0 = wxString::npos;
980  }
981  #else
982  filename.Replace( wxT( "\\" ), wxT( "/" ) );
983  #endif
984 
985  // names may not end with ':'
986  if( pos0 == aFileName.length() -1 )
987  return false;
988 
989  if( pos0 != wxString::npos )
990  {
991  // ensure the alias component is not empty
992  if( pos0 == 0 )
993  return false;
994 
995  wxString lpath = filename.substr( 0, pos0 );
996 
997  // check the alias for restricted characters
998  if( wxString::npos != lpath.find_first_of( wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" ) ) )
999  return false;
1000 
1001  hasAlias = true;
1002  }
1003 
1004  return true;
1005 }
1006 
1007 
1008 bool FILENAME_RESOLVER::GetKicadPaths( std::list< wxString >& paths )
1009 {
1010  paths.clear();
1011 
1012  if( !m_pgm )
1013  return false;
1014 
1015  bool hasKisys3D = false;
1016 
1017 
1018  // iterate over the list of internally defined ENV VARs
1019  // and add them to the paths list
1022 
1023  while( mS != mE )
1024  {
1025  // filter out URLs, template directories, and known system paths
1026  if( mS->first == wxString( "KICAD_PTEMPLATES" )
1027  || mS->first == wxString( "KISYSMOD" ) )
1028  {
1029  ++mS;
1030  continue;
1031  }
1032 
1033  if( wxString::npos != mS->second.GetValue().find( wxString( "://" ) ) )
1034  {
1035  ++mS;
1036  continue;
1037  }
1038 
1039  wxString tmp( "${" );
1040  tmp.Append( mS->first );
1041  tmp.Append( "}" );
1042  paths.push_back( tmp );
1043 
1044  if( tmp == "${KISYS3DMOD}" )
1045  hasKisys3D = true;
1046 
1047  ++mS;
1048  }
1049 
1050  if( !hasKisys3D )
1051  paths.emplace_back("${KISYS3DMOD}" );
1052 
1053  return true;
1054 }
#define CFGFILE_VERSION
std::list< SEARCH_PATH > m_paths
PROJECT holds project specific data.
Definition: project.h:63
#define ERRFLG_ENVPATH
bool readPathList(void)
Function readPathList reads a list of path names from a configuration file.
PGM_BASE keeps program (whole process) data for KiCad programs.
Definition: pgm_base.h:137
const wxChar *const tracePathsAndFiles
Flag to enable path and file name debug output.
provides an extensible class to resolve 3D model paths.
bool writePathList(void)
Function writePathList writes the current path list to a configuration file.
bool createPathList(void)
Function createPathList builds the path list using available information such as KISYS3DMOD and the 3...
const std::list< SEARCH_PATH > * GetPaths(void)
Function GetPaths returns a pointer to the internal path list; the items in:load.
static bool getHollerith(const std::string &aString, size_t &aIndex, wxString &aResult)
const wxString ExpandEnvVarSubstitutions(const wxString &aString, PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:255
VTBL_ENTRY const wxString GetProjectPath() const
Function GetProjectPath returns the full path of the project.
Definition: project.cpp:122
bool SetProject(PROJECT *aProject, bool *flgChanged=NULL)
Function SetProjectDir sets the current KiCad project directory as the first entry in the model path ...
wxString m_Alias
bool SplitAlias(const wxString &aFileName, wxString &anAlias, wxString &aRelPath)
Function SplitAlias returns true if the given name contains an alias and populates the string anAlias...
bool UpdatePathList(std::vector< SEARCH_PATH > &aPathList)
Function UpdatePathList clears the current path list and substitutes the given path list,...
#define ERRFLG_ALIAS
bool addPath(const SEARCH_PATH &aPath)
Function addPath checks that a path is valid and adds it to the search list.
wxString ShortenPath(const wxString &aFullPathName)
Function ShortenPath produces a relative path based on the existing search directories or returns the...
std::map< wxString, ENV_VAR_ITEM >::const_iterator ENV_VAR_MAP_CITER
Definition: pgm_base.h:119
wxString m_Description
bool GetKicadPaths(std::list< wxString > &paths)
Function GetKicadPaths returns a list of path environment variables local to Kicad; this list always ...
wxString GetProjectDir(void)
#define ERRFLG_RELPATH
wxString m_Pathvar
wxLogTrace helper definitions.
VTBL_ENTRY const ENV_VAR_MAP & GetLocalEnvVariables() const
Definition: pgm_base.h:298
wxString ResolvePath(const wxString &aFileName)
Function ResolvePath determines the full path of the given file name.
#define RESOLVER_CONFIG
see class PGM_BASE
static std::mutex mutex_resolver
#define _(s)
Definition: 3d_actions.cpp:33
The common library.
bool Set3DConfigDir(const wxString &aConfigDir)
Function Set3DConfigDir sets the user's configuration directory for 3D models.
void checkEnvVarPath(const wxString &aPath)
Function checkEnvVarPath checks the ${ENV_VAR} component of a path and adds it to the resolver's path...
void SetProgramBase(PGM_BASE *aBase)
Function SetProgramBase sets a pointer to the application's PGM_BASE instance; the pointer is used to...
bool ValidateFileName(const wxString &aFileName, bool &hasAlias)
Function ValidateName returns true if the given path is a valid aliased relative path.
#define MASK_3D_RESOLVER
wxString m_Pathexp