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 
33 #include "common.h"
34 #include "3d_filename_resolver.h"
35 
36 // configuration file version
37 #define CFGFILE_VERSION 1
38 #define S3D_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 lock3D_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 S3D_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( false == cfgdir.DirExists() )
74  return false;
75 
76  m_ConfigDir = cfgdir.GetPath();
78 
79  return true;
80 }
81 
82 
83 bool S3D_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( false == 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  S3D_ALIAS 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( NULL == m_pgm || m_Paths.empty() )
158  return;
159 
160  // recreate the path list
161  m_Paths.clear();
162  createPathList();
163 
164  return;
165 }
166 
167 
169 {
170  if( !m_Paths.empty() )
171  return true;
172 
173  wxString kmod;
174 
175  // add an entry for the default search path; at this point
176  // we cannot set a sensible default so we use an empty string.
177  // the user may change this later with a call to SetProjectDir()
178 
179  S3D_ALIAS lpath;
180  lpath.m_alias = "${KIPRJMOD}";
181  lpath.m_pathvar = "${KIPRJMOD}";
182  lpath.m_pathexp = m_curProjDir;
183  m_Paths.push_back( lpath );
184  wxFileName fndummy;
185  wxUniChar psep = fndummy.GetPathSeparator();
186  std::list< wxString > epaths;
187 
188  if( GetKicadPaths( epaths ) )
189  {
190  for( auto i : epaths )
191  {
192  wxString pathVal = ExpandEnvVarSubstitutions( i );
193 
194  if( pathVal.empty() )
195  {
196  lpath.m_pathexp.clear();
197  }
198  else
199  {
200  fndummy.Assign( pathVal, "" );
201  fndummy.Normalize();
202  lpath.m_pathexp = fndummy.GetFullPath();
203  }
204 
205  lpath.m_alias = i;
206  lpath.m_pathvar = i;
207 
208  if( !lpath.m_pathexp.empty() && psep == *lpath.m_pathexp.rbegin() )
209  lpath.m_pathexp.erase( --lpath.m_pathexp.end() );
210 
211  m_Paths.push_back( lpath );
212  }
213  }
214 
215  if( !m_ConfigDir.empty() )
216  readPathList();
217 
218  if( m_Paths.empty() )
219  return false;
220 
221 #ifdef DEBUG
222  wxLogTrace( MASK_3D_RESOLVER, " * [3D model] search paths:\n" );
223  std::list< S3D_ALIAS >::const_iterator sPL = m_Paths.begin();
224  std::list< S3D_ALIAS >::const_iterator ePL = m_Paths.end();
225 
226  while( sPL != ePL )
227  {
228  wxLogTrace( MASK_3D_RESOLVER, " + %s : '%s'\n", (*sPL).m_alias.GetData(),
229  (*sPL).m_pathexp.GetData() );
230  ++sPL;
231  }
232 #endif
233 
234  return true;
235 }
236 
237 
238 bool S3D_FILENAME_RESOLVER::UpdatePathList( std::vector< S3D_ALIAS >& aPathList )
239 {
240  wxUniChar envMarker( '$' );
241 
242  while( !m_Paths.empty() && envMarker != *m_Paths.back().m_alias.rbegin() )
243  m_Paths.pop_back();
244 
245  size_t nI = aPathList.size();
246 
247  for( size_t i = 0; i < nI; ++i )
248  addPath( aPathList[i] );
249 
250  return writePathList();
251 }
252 
253 
254 wxString S3D_FILENAME_RESOLVER::ResolvePath( const wxString& aFileName )
255 {
256  wxCriticalSectionLocker lock( lock3D_resolver );
257 
258  if( aFileName.empty() )
259  return wxEmptyString;
260 
261  if( m_Paths.empty() )
262  createPathList();
263 
264  // first attempt to use the name as specified:
265  wxString tname = aFileName;
266 
267  #ifdef _WIN32
268  // translate from KiCad's internal UNIX-like path to MSWin paths
269  tname.Replace( wxT( "/" ), wxT( "\\" ) );
270  #endif
271 
272  // Note: variable expansion must be performed using a threadsafe
273  // wrapper for the getenv() system call. If we allow the
274  // wxFileName::Normalize() routine to perform expansion then
275  // we will have a race condition since wxWidgets does not assure
276  // a threadsafe wrapper for getenv().
277  if( tname.StartsWith( "${" ) || tname.StartsWith( "$(" ) )
278  tname = ExpandEnvVarSubstitutions( tname );
279 
280  wxFileName tmpFN( tname );
281 
282  // in the case of absolute filenames we don't store a map item
283  if( !aFileName.StartsWith( "${" ) && !aFileName.StartsWith( "$(" )
284  && !aFileName.StartsWith( ":" ) && tmpFN.IsAbsolute() )
285  {
286  tmpFN.Normalize();
287 
288  if( tmpFN.FileExists() )
289  return tmpFN.GetFullPath();
290 
291  return wxEmptyString;
292  }
293 
294  // this case covers full paths, leading expanded vars, and paths
295  // relative to the current working directory (which is not necessarily
296  // the current project directory)
297  if( tmpFN.FileExists() )
298  {
299  tmpFN.Normalize();
300  tname = tmpFN.GetFullPath();
301 
302  // special case: if a path begins with ${ENV_VAR} but is not in the
303  // resolver's path list then add it.
304  if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
305  checkEnvVarPath( aFileName );
306 
307  return tname;
308  }
309 
310  // if a path begins with ${ENV_VAR}/$(ENV_VAR) and is not resolved then the
311  // file either does not exist or the ENV_VAR is not defined
312  if( aFileName.StartsWith( "${" ) || aFileName.StartsWith( "$(" ) )
313  {
314  if( !( m_errflags & ERRFLG_ENVPATH ) )
315  {
317  wxString errmsg = "[3D File Resolver] No such path; ensure the environment var is defined";
318  errmsg.append( "\n" );
319  errmsg.append( tname );
320  wxLogMessage( errmsg );
321  }
322 
323  return wxEmptyString;
324  }
325 
326  // at this point aFileName is:
327  // a. an aliased shortened name or
328  // b. cannot be determined
329 
330  std::list< S3D_ALIAS >::const_iterator sPL = m_Paths.begin();
331  std::list< S3D_ALIAS >::const_iterator ePL = m_Paths.end();
332 
333  // check the path relative to the current project directory;
334  // note: this is not necessarily the same as the current working
335  // directory, which has already been checked. This case accounts
336  // for partial paths which do not contain ${KIPRJMOD}.
337  // This check is performed before checking the path relative to
338  // ${KISYS3DMOD} so that users can potentially override a model
339  // within ${KISYS3DMOD}
340  if( !sPL->m_pathexp.empty() && !tname.StartsWith( ":" ) )
341  {
342  tmpFN.Assign( sPL->m_pathexp, "" );
343  wxString fullPath = tmpFN.GetPathWithSep() + tname;
344 
345  if( fullPath.StartsWith( "${" ) || fullPath.StartsWith( "$(" ) )
346  fullPath = ExpandEnvVarSubstitutions( fullPath );
347 
348  if( wxFileName::FileExists( fullPath ) )
349  {
350  tmpFN.Assign( fullPath );
351  tmpFN.Normalize();
352  tname = tmpFN.GetFullPath();
353  return tname;
354  }
355 
356  }
357 
358  // check the partial path relative to ${KISYS3DMOD} (legacy behavior)
359  if( !tname.StartsWith( ":" ) )
360  {
361  wxFileName fpath;
362  wxString fullPath( "${KISYS3DMOD}" );
363  fullPath.Append( fpath.GetPathSeparator() );
364  fullPath.Append( tname );
365  fullPath = ExpandEnvVarSubstitutions( fullPath );
366  fpath.Assign( fullPath );
367 
368  if( fpath.Normalize() && fpath.FileExists() )
369  {
370  tname = fpath.GetFullPath();
371  return tname;
372  }
373 
374  }
375 
376  // ${ENV_VAR} paths have already been checked; skip them
377  while( sPL != ePL && ( sPL->m_alias.StartsWith( "${" )
378  || sPL->m_alias.StartsWith( "$(" ) ) )
379  ++sPL;
380 
381  // at this point the filename must contain an alias or else it is invalid
382  wxString alias; // the alias portion of the short filename
383  wxString relpath; // the path relative to the alias
384 
385  if( !SplitAlias( tname, alias, relpath ) )
386  {
387  if( !( m_errflags & ERRFLG_RELPATH ) )
388  {
389  // this can happen if the file was intended to be relative to
390  // ${KISYS3DMOD} but ${KISYS3DMOD} not set or incorrect.
392  wxString errmsg = "[3D File Resolver] No such path";
393  errmsg.append( "\n" );
394  errmsg.append( tname );
395  wxLogTrace( MASK_3D_RESOLVER, errmsg );
396  }
397 
398  return wxEmptyString;
399  }
400 
401  while( sPL != ePL )
402  {
403  if( !sPL->m_alias.Cmp( alias ) && !sPL->m_pathexp.empty() )
404  {
405  wxFileName fpath( wxFileName::DirName( sPL->m_pathexp ) );
406  wxString fullPath = fpath.GetPathWithSep() + relpath;
407 
408  if( fullPath.StartsWith( "${") || fullPath.StartsWith( "$(" ) )
409  fullPath = ExpandEnvVarSubstitutions( fullPath );
410 
411  if( wxFileName::FileExists( fullPath ) )
412  {
413  wxFileName tmp( fullPath );
414 
415  if( tmp.Normalize() )
416  tname = tmp.GetFullPath();
417 
418  return tname;
419  }
420  }
421 
422  ++sPL;
423  }
424 
425  if( !( m_errflags & ERRFLG_ALIAS ) )
426  {
428  wxString errmsg = "[3D File Resolver] No such path; ensure the path alias is defined";
429  errmsg.append( "\n" );
430  errmsg.append( tname.substr( 1 ) );
431  wxLogTrace( MASK_3D_RESOLVER, 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( lock3D_resolver );
444 
445  S3D_ALIAS 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< S3D_ALIAS >::iterator sPL = m_Paths.begin();
493  std::list< S3D_ALIAS >::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, S3D_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  S3D_ALIAS 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  if( m_Paths.size() != nitems )
620  return true;
621 
622  return false;
623 }
624 
625 
627 {
628  if( m_ConfigDir.empty() )
629  {
630  std::ostringstream ostr;
631  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
632  wxString errmsg = _( "3D configuration directory is unknown" );
633  ostr << " * " << errmsg.ToUTF8();
634  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
635  wxMessageBox( errmsg, _( "Write 3D search path list" ) );
636 
637  return false;
638  }
639 
640  // skip all ${ENV_VAR} alias names
641  std::list< S3D_ALIAS >::const_iterator sPL = m_Paths.begin();
642  std::list< S3D_ALIAS >::const_iterator ePL = m_Paths.end();
643 
644  while( sPL != ePL && ( sPL->m_alias.StartsWith( "${" )
645  || sPL->m_alias.StartsWith( "$(" ) ) )
646  ++sPL;
647 
648  wxFileName cfgpath( m_ConfigDir, S3D_RESOLVER_CONFIG );
649  wxString cfgname = cfgpath.GetFullPath();
650  std::ofstream cfgFile;
651 
652  if( sPL == ePL )
653  {
654  wxMessageDialog md( NULL,
655  _( "3D search path list is empty;\ncontinue to write empty file?" ),
656  _( "Write 3D search path list" ), wxYES_NO );
657 
658  if( md.ShowModal() == wxID_YES )
659  {
660  cfgFile.open( cfgname.ToUTF8(), std::ios_base::trunc );
661 
662  if( !cfgFile.is_open() )
663  {
664  wxMessageBox( _( "Could not open configuration file" ),
665  _( "Write 3D search path list" ) );
666 
667  return false;
668  }
669 
670  cfgFile << "#V" << CFGFILE_VERSION << "\n";
671  cfgFile.close();
672  return true;
673  }
674 
675  return false;
676  }
677 
678  cfgFile.open( cfgname.ToUTF8(), std::ios_base::trunc );
679 
680  if( !cfgFile.is_open() )
681  {
682  std::ostringstream ostr;
683  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
684  wxString errmsg = _( "Could not open configuration file" );
685  ostr << " * " << errmsg.ToUTF8() << " '" << cfgname.ToUTF8() << "'";
686  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
687  wxMessageBox( errmsg, _( "Write 3D search path list" ) );
688 
689  return false;
690  }
691 
692  cfgFile << "#V" << CFGFILE_VERSION << "\n";
693  std::string tstr;
694 
695  while( sPL != ePL )
696  {
697  tstr = sPL->m_alias.ToUTF8();
698  cfgFile << "\"" << tstr.size() << ":" << tstr << "\",";
699  tstr = sPL->m_pathvar.ToUTF8();
700  cfgFile << "\"" << tstr.size() << ":" << tstr << "\",";
701  tstr = sPL->m_description.ToUTF8();
702  cfgFile << "\"" << tstr.size() << ":" << tstr << "\"\n";
703  ++sPL;
704  }
705 
706  bool bad = cfgFile.bad();
707  cfgFile.close();
708 
709  if( bad )
710  {
711  wxMessageBox( _( "Problems writing configuration file" ),
712  _( "Write 3D search path list" ) );
713 
714  return false;
715  }
716 
717  return true;
718 }
719 
720 
721 void S3D_FILENAME_RESOLVER::checkEnvVarPath( const wxString& aPath )
722 {
723  bool useParen = false;
724 
725  if( aPath.StartsWith( "$(" ) )
726  useParen = true;
727  else if( !aPath.StartsWith( "${" ) )
728  return;
729 
730  size_t pEnd;
731 
732  if( useParen )
733  pEnd = aPath.find( ")" );
734  else
735  pEnd = aPath.find( "}" );
736 
737  if( pEnd == wxString::npos )
738  return;
739 
740  wxString envar = aPath.substr( 0, pEnd + 1 );
741 
742  // check if the alias exists; if not then add it to the end of the
743  // env var section of the path list
744  auto sPL = m_Paths.begin();
745  auto ePL = m_Paths.end();
746 
747  while( sPL != ePL )
748  {
749  if( sPL->m_alias == envar )
750  return;
751 
752  if( !sPL->m_alias.StartsWith( "${" ) )
753  break;
754 
755  ++sPL;
756  }
757 
758  S3D_ALIAS lpath;
759  lpath.m_alias = envar;
760  lpath.m_pathvar = lpath.m_alias;
761  wxFileName tmpFN;
762 
763  if( lpath.m_alias.StartsWith( "${" ) || lpath.m_alias.StartsWith( "$(" ) )
764  tmpFN.Assign( ExpandEnvVarSubstitutions( lpath.m_alias ), "" );
765  else
766  tmpFN.Assign( lpath.m_alias, "" );
767 
768  wxUniChar psep = tmpFN.GetPathSeparator();
769  tmpFN.Normalize();
770 
771  if( !tmpFN.DirExists() )
772  return;
773 
774  lpath.m_pathexp = tmpFN.GetFullPath();
775 
776  if( !lpath.m_pathexp.empty() && psep == *lpath.m_pathexp.rbegin() )
777  lpath.m_pathexp.erase( --lpath.m_pathexp.end() );
778 
779  if( lpath.m_pathexp.empty() )
780  return;
781 
782  m_Paths.insert( sPL, lpath );
783  return;
784 }
785 
786 
787 wxString S3D_FILENAME_RESOLVER::ShortenPath( const wxString& aFullPathName )
788 {
789  wxString fname = aFullPathName;
790 
791  if( m_Paths.empty() )
792  createPathList();
793 
794  wxCriticalSectionLocker lock( lock3D_resolver );
795  std::list< S3D_ALIAS >::const_iterator sL = m_Paths.begin();
796  std::list< S3D_ALIAS >::const_iterator eL = m_Paths.end();
797  size_t idx;
798 
799  while( sL != eL )
800  {
801  // undefined paths do not participate in the
802  // file name shortening procedure
803  if( sL->m_pathexp.empty() )
804  {
805  ++sL;
806  continue;
807  }
808 
809  wxFileName fpath;
810 
811  // in the case of aliases, ensure that we use the most recent definition
812  if( sL->m_alias.StartsWith( "${" ) || sL->m_alias.StartsWith( "$(" ) )
813  {
814  wxString tpath = ExpandEnvVarSubstitutions( sL->m_alias );
815 
816  if( tpath.empty() )
817  {
818  ++sL;
819  continue;
820  }
821 
822  fpath.Assign( tpath, wxT( "" ) );
823  }
824  else
825  {
826  fpath.Assign( sL->m_pathexp, wxT( "" ) );
827  }
828 
829  wxString fps = fpath.GetPathWithSep();
830  wxString tname;
831 
832  idx = fname.find( fps );
833 
834  if( std::string::npos != idx && 0 == idx )
835  {
836  fname = fname.substr( fps.size() );
837 
838  #ifdef _WIN32
839  // ensure only the '/' separator is used in the internal name
840  fname.Replace( wxT( "\\" ), wxT( "/" ) );
841  #endif
842 
843  if( sL->m_alias.StartsWith( "${" ) || sL->m_alias.StartsWith( "$(" ) )
844  {
845  // old style ENV_VAR
846  tname = sL->m_alias;
847  tname.Append( "/" );
848  tname.append( fname );
849  }
850  else
851  {
852  // new style alias
853  tname = ":";
854  tname.append( sL->m_alias );
855  tname.append( ":" );
856  tname.append( fname );
857  }
858 
859  return tname;
860  }
861 
862  ++sL;
863  }
864 
865 #ifdef _WIN32
866  // it is strange to convert an MSWin full path to use the
867  // UNIX separator but this is done for consistency and can
868  // be helpful even when transferring project files from
869  // MSWin to *NIX.
870  fname.Replace( wxT( "\\" ), wxT( "/" ) );
871 #endif
872 
873  return fname;
874 }
875 
876 
877 
878 const std::list< S3D_ALIAS >* S3D_FILENAME_RESOLVER::GetPaths( void )
879 {
880  return &m_Paths;
881 }
882 
883 
884 bool S3D_FILENAME_RESOLVER::SplitAlias( const wxString& aFileName,
885  wxString& anAlias, wxString& aRelPath )
886 {
887  anAlias.clear();
888  aRelPath.clear();
889 
890  if( !aFileName.StartsWith( wxT( ":" ) ) )
891  return false;
892 
893  size_t tagpos = aFileName.find( wxT( ":" ), 1 );
894 
895  if( wxString::npos == tagpos || 1 == tagpos )
896  return false;
897 
898  if( tagpos + 1 >= aFileName.length() )
899  return false;
900 
901  anAlias = aFileName.substr( 1, tagpos - 1 );
902  aRelPath = aFileName.substr( tagpos + 1 );
903 
904  return true;
905 }
906 
907 
908 static bool getHollerith( const std::string& aString, size_t& aIndex, wxString& aResult )
909 {
910  aResult.clear();
911 
912  if( aIndex >= aString.size() )
913  {
914  std::ostringstream ostr;
915  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
916  wxString errmsg = "bad Hollerith string on line";
917  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
918  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
919 
920  return false;
921  }
922 
923  size_t i2 = aString.find( '"', aIndex );
924 
925  if( std::string::npos == i2 )
926  {
927  std::ostringstream ostr;
928  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
929  wxString errmsg = "missing opening quote mark in config file";
930  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
931  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
932 
933  return false;
934  }
935 
936  ++i2;
937 
938  if( i2 >= aString.size() )
939  {
940  std::ostringstream ostr;
941  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
942  wxString errmsg = "invalid entry (unexpected end of line)";
943  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
944  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
945 
946  return false;
947  }
948 
949  std::string tnum;
950 
951  while( aString[i2] >= '0' && aString[i2] <= '9' )
952  tnum.append( 1, aString[i2++] );
953 
954  if( tnum.empty() || aString[i2++] != ':' )
955  {
956  std::ostringstream ostr;
957  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
958  wxString errmsg = "bad Hollerith string on line";
959  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
960  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
961 
962  return false;
963  }
964 
965  std::istringstream istr;
966  istr.str( tnum );
967  size_t nchars;
968  istr >> nchars;
969 
970  if( (i2 + nchars) >= aString.size() )
971  {
972  std::ostringstream ostr;
973  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
974  wxString errmsg = "invalid entry (unexpected end of line)";
975  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
976  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
977 
978  return false;
979  }
980 
981  if( nchars > 0 )
982  {
983  aResult = aString.substr( i2, nchars );
984  i2 += nchars;
985  }
986 
987  if( aString[i2] != '"' )
988  {
989  std::ostringstream ostr;
990  ostr << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << "\n";
991  wxString errmsg = "missing closing quote mark in config file";
992  ostr << " * " << errmsg.ToUTF8() << "\n'" << aString << "'";
993  wxLogTrace( MASK_3D_RESOLVER, "%s\n", ostr.str().c_str() );
994 
995  return false;
996  }
997 
998  aIndex = i2 + 1;
999  return true;
1000 }
1001 
1002 
1003 bool S3D_FILENAME_RESOLVER::ValidateFileName( const wxString& aFileName, bool& hasAlias )
1004 {
1005  // Rules:
1006  // 1. The generic form of an aliased 3D relative path is:
1007  // ALIAS:relative/path
1008  // 2. ALIAS is a UTF string excluding wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" )
1009  // 3. The relative path must be a valid relative path for the platform
1010  hasAlias = false;
1011 
1012  if( aFileName.empty() )
1013  return false;
1014 
1015  wxString filename = aFileName;
1016  size_t pos0 = aFileName.find( ':' );
1017 
1018  // ensure that the file separators suit the current platform
1019  #ifdef __WINDOWS__
1020  filename.Replace( wxT( "/" ), wxT( "\\" ) );
1021 
1022  // if we see the :\ pattern then it must be a drive designator
1023  if( pos0 != wxString::npos )
1024  {
1025  size_t pos1 = filename.find( wxT( ":\\" ) );
1026 
1027  if( pos1 != wxString::npos && ( pos1 != pos0 || pos1 != 1 ) )
1028  return false;
1029 
1030  // if we have a drive designator then we have no alias
1031  if( pos1 != wxString::npos )
1032  pos0 = wxString::npos;
1033  }
1034  #else
1035  filename.Replace( wxT( "\\" ), wxT( "/" ) );
1036  #endif
1037 
1038  // names may not end with ':'
1039  if( pos0 == aFileName.length() -1 )
1040  return false;
1041 
1042  if( pos0 != wxString::npos )
1043  {
1044  // ensure the alias component is not empty
1045  if( pos0 == 0 )
1046  return false;
1047 
1048  wxString lpath = filename.substr( 0, pos0 );
1049 
1050  // check the alias for restricted characters
1051  if( wxString::npos != lpath.find_first_of( wxT( "{}[]()%~<>\"='`;:.,&?/\\|$" ) ) )
1052  return false;
1053 
1054  hasAlias = true;
1055  }
1056 
1057  return true;
1058 }
1059 
1060 
1061 bool S3D_FILENAME_RESOLVER::GetKicadPaths( std::list< wxString >& paths )
1062 {
1063  paths.clear();
1064 
1065  if( !m_pgm )
1066  return false;
1067 
1068  bool hasKisys3D = false;
1069 
1070 
1071  // iterate over the list of internally defined ENV VARs
1072  // and add them to the paths list
1075 
1076  while( mS != mE )
1077  {
1078  // filter out URLs, template directories, and known system paths
1079  if( mS->first == wxString( "KICAD_PTEMPLATES" )
1080  || mS->first == wxString( "KIGITHUB" )
1081  || mS->first == wxString( "KISYSMOD" ) )
1082  {
1083  ++mS;
1084  continue;
1085  }
1086 
1087  if( wxString::npos != mS->second.GetValue().find( wxString( "://" ) ) )
1088  {
1089  ++mS;
1090  continue;
1091  }
1092 
1093  wxString tmp( "${" );
1094  tmp.Append( mS->first );
1095  tmp.Append( "}" );
1096  paths.push_back( tmp );
1097 
1098  if( tmp == "${KISYS3DMOD}" )
1099  hasKisys3D = true;
1100 
1101  ++mS;
1102  }
1103 
1104  if( !hasKisys3D )
1105  paths.push_back( "${KISYS3DMOD}" );
1106 
1107  return true;
1108 }
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.
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'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)
Function ExpandEnvVarSubstitutions replaces any environment variable references with their values...
Definition: common.cpp:254
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's path...
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.
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's configuration directory for 3D models.