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