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