KiCad PCB EDA Suite
Loading...
Searching...
No Matches
gestfich.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) 2004 Jean-Pierre Charras, [email protected]
5 * Copyright (C) 2008 Wayne Stambaugh <[email protected]>
6 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License
10 * as published by the Free Software Foundation; either version 2
11 * of the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
26
27#include <wx/mimetype.h>
28#include <wx/dir.h>
29
30#include <pgm_base.h>
31#include <confirm.h>
32#include <core/arraydim.h>
33#include <gestfich.h>
34#include <string_utils.h>
35#include <launch_ext.h>
36#include "wx/tokenzr.h"
37#include <richio.h>
38#include <sexpr/sexpr.h>
39#include <sexpr/sexpr_parser.h>
40
41#include <wx/wfstream.h>
42#include <wx/fs_zip.h>
43#include <wx/zipstrm.h>
44
45#include <filesystem>
46#include <string>
47#include <system_error>
48#include <unordered_set>
49#include <core/kicad_algo.h>
50
51void QuoteString( wxString& string )
52{
53 if( !string.StartsWith( wxT( "\"" ) ) )
54 {
55 string.Prepend ( wxT( "\"" ) );
56 string.Append ( wxT( "\"" ) );
57 }
58}
59
60
61wxString FindKicadFile( const wxString& shortname )
62{
63 // Test the presence of the file in the directory shortname of
64 // the KiCad binary path.
65#ifndef __WXMAC__
66 wxString fullFileName = Pgm().GetExecutablePath() + shortname;
67#else
68 wxString fullFileName = Pgm().GetExecutablePath() + wxT( "Contents/MacOS/" ) + shortname;
69#endif
70 if( wxFileExists( fullFileName ) )
71 return fullFileName;
72
73 if( wxGetEnv( wxT( "KICAD_RUN_FROM_BUILD_DIR" ), nullptr ) )
74 {
75 wxFileName buildDir( Pgm().GetExecutablePath(), shortname );
76
77#ifdef __WXMAC__
78 if( !buildDir.GetDirs().IsEmpty()
79 && buildDir.GetDirs().Last().Lower().EndsWith( wxT( ".app" ) ) )
80 {
81 buildDir.RemoveLastDir();
82 }
83#endif
84 buildDir.RemoveLastDir();
85#ifndef __WXMSW__
86 buildDir.AppendDir( shortname );
87#else
88 buildDir.AppendDir( shortname.BeforeLast( '.' ) );
89#endif
90
91 if( buildDir.GetDirs().Last() == "pl_editor" )
92 {
93 buildDir.RemoveLastDir();
94 buildDir.AppendDir( "pagelayout_editor" );
95 }
96
97#ifdef __WXMAC__
98 buildDir.AppendDir( shortname + wxT( ".app" ) );
99 buildDir.AppendDir( wxT( "Contents" ) );
100 buildDir.AppendDir( wxT( "MacOS" ) );
101#endif
102
103 if( wxFileExists( buildDir.GetFullPath() ) )
104 return buildDir.GetFullPath();
105 }
106
107 // Test the presence of the file in the directory shortname
108 // defined by the environment variable KiCad.
109 if( Pgm().IsKicadEnvVariableDefined() )
110 {
111 fullFileName = Pgm().GetKicadEnvVariable() + shortname;
112
113 if( wxFileExists( fullFileName ) )
114 return fullFileName;
115 }
116
117#if defined( __WINDOWS__ )
118 // KiCad can be installed highly portably on Windows, anywhere and concurrently
119 // either the "kicad file" is immediately adjacent to the exe or it's not a valid install
120 return shortname;
121#else
122
123 // Path list for KiCad binary files
124 const static wxChar* possibilities[] = {
125#if defined( __WXMAC__ )
126 // all internal paths are relative to main bundle kicad.app
127 wxT( "Contents/Applications/pcbnew.app/Contents/MacOS/" ),
128 wxT( "Contents/Applications/eeschema.app/Contents/MacOS/" ),
129 wxT( "Contents/Applications/gerbview.app/Contents/MacOS/" ),
130 wxT( "Contents/Applications/bitmap2component.app/Contents/MacOS/" ),
131 wxT( "Contents/Applications/pcb_calculator.app/Contents/MacOS/" ),
132 wxT( "Contents/Applications/pl_editor.app/Contents/MacOS/" ),
133#else
134 wxT( "/usr/bin/" ),
135 wxT( "/usr/local/bin/" ),
136 wxT( "/usr/local/kicad/bin/" ),
137#endif
138 };
139
140 // find binary file from possibilities list:
141 for( unsigned i=0; i<arrayDim(possibilities); ++i )
142 {
143#ifndef __WXMAC__
144 fullFileName = possibilities[i] + shortname;
145#else
146 // make relative paths absolute
147 fullFileName = Pgm().GetExecutablePath() + possibilities[i] + shortname;
148#endif
149
150 if( wxFileExists( fullFileName ) )
151 return fullFileName;
152 }
153
154 return shortname;
155
156#endif
157}
158
159
160int ExecuteFile( const wxString& aEditorName, const wxString& aFileName, wxProcess* aCallback,
161 bool aFileForKicad )
162{
163 wxString fullEditorName;
164 std::vector<wxString> params;
165
166#ifdef __UNIX__
167 wxString param;
168 bool inSingleQuotes = false;
169 bool inDoubleQuotes = false;
170
171 auto pushParam =
172 [&]()
173 {
174 if( !param.IsEmpty() )
175 {
176 params.push_back( param );
177 param.clear();
178 }
179 };
180
181 for( wxUniChar ch : aEditorName )
182 {
183 if( inSingleQuotes )
184 {
185 if( ch == '\'' )
186 {
187 pushParam();
188 inSingleQuotes = false;
189 continue;
190 }
191 else
192 {
193 param += ch;
194 }
195 }
196 else if( inDoubleQuotes )
197 {
198 if( ch == '"' )
199 {
200 pushParam();
201 inDoubleQuotes = false;
202 }
203 else
204 {
205 param += ch;
206 }
207 }
208 else if( ch == '\'' )
209 {
210 pushParam();
211 inSingleQuotes = true;
212 }
213 else if( ch == '"' )
214 {
215 pushParam();
216 inDoubleQuotes = true;
217 }
218 else if( ch == ' ' )
219 {
220 pushParam();
221 }
222 else
223 {
224 param += ch;
225 }
226 }
227
228 pushParam();
229
230 if( aFileForKicad )
231 fullEditorName = FindKicadFile( params[0] );
232 else
233 fullEditorName = params[0];
234
235 params.erase( params.begin() );
236#else
237
238 if( aFileForKicad )
239 fullEditorName = FindKicadFile( aEditorName );
240 else
241 fullEditorName = aEditorName;
242#endif
243
244 if( wxFileExists( fullEditorName ) )
245 {
246 std::vector<const wchar_t*> args;
247
248 args.emplace_back( fullEditorName.wc_str() );
249
250 if( !params.empty() )
251 {
252 for( const wxString& p : params )
253 args.emplace_back( p.wc_str() );
254 }
255
256 if( !aFileName.IsEmpty() )
257 args.emplace_back( aFileName.wc_str() );
258
259 args.emplace_back( nullptr );
260
261 return wxExecute( const_cast<wchar_t**>( args.data() ), wxEXEC_ASYNC, aCallback );
262 }
263
264 wxString msg;
265 msg.Printf( _( "Command '%s' could not be found." ), fullEditorName );
266 DisplayErrorMessage( nullptr, msg );
267 return -1;
268}
269
270
271int ExecuteCommandThroughShell( const wxString& aCommand, wxProcess* aProcess )
272{
273#ifdef __WXMSW__
274 // The array form of wxExecute is unusable with cmd.exe. wx joins the argv elements back into a
275 // single command line, wrapping any element containing spaces in double quotes and escaping
276 // embedded quotes with backslashes. cmd.exe does not understand backslash-escaped quotes and
277 // applies its own quote-stripping rules to the /c argument, which mangles absolute paths that
278 // contain spaces or quotes. Build the command line ourselves and let cmd.exe's /s rule strip
279 // exactly the outer quote pair, passing everything between through verbatim. /d disables any
280 // AutoRun registry commands so job execution is not machine-dependent.
281 wxString shellCmd = wxS( "cmd.exe /d /s /c \"" ) + aCommand + wxS( "\"" );
282
283 return static_cast<int>( wxExecute( shellCmd, wxEXEC_SYNC, aProcess ) );
284#else
285 // Invoke /bin/sh -c so glob expansion, pipes, and other shell features work. The string form of
286 // wxExecute would call execvp() directly, bypassing the shell. Hold the wchar buffers in named
287 // locals so the argv pointers stay valid on wxUSE_UNICODE_UTF8 builds where wc_str() is a temp.
288 wxWCharBuffer shell = wxString( wxS( "/bin/sh" ) ).wc_str();
289 wxWCharBuffer flag = wxString( wxS( "-c" ) ).wc_str();
290 wxWCharBuffer command = aCommand.wc_str();
291
292 const wchar_t* argv[] = { shell.data(), flag.data(), command.data(), nullptr };
293
294 return static_cast<int>( wxExecute( argv, wxEXEC_SYNC, aProcess ) );
295#endif
296}
297
298
299bool OpenPDF( const wxString& file )
300{
301 wxString msg;
302 wxString filename = file;
303
305
306 if( Pgm().UseSystemPdfBrowser() )
307 {
308 if( !LaunchExternal( filename ) )
309 {
310 msg.Printf( _( "Unable to find a PDF viewer for '%s'." ), filename );
311 DisplayErrorMessage( nullptr, msg );
312 return false;
313 }
314 }
315 else
316 {
317 const wchar_t* args[3];
318
319 args[0] = Pgm().GetPdfBrowserName().wc_str();
320 args[1] = filename.wc_str();
321 args[2] = nullptr;
322
323 if( wxExecute( const_cast<wchar_t**>( args ) ) == -1 )
324 {
325 msg.Printf( _( "Problem while running the PDF viewer '%s'." ), args[0] );
326 DisplayErrorMessage( nullptr, msg );
327 return false;
328 }
329 }
330
331 return true;
332}
333
334
335void KiCopyFile( const wxString& aSrcPath, const wxString& aDestPath, wxString& aErrors )
336{
337 if( !wxCopyFile( aSrcPath, aDestPath ) )
338 {
339 wxString msg;
340
341 if( !aErrors.IsEmpty() )
342 aErrors += "\n";
343
344 msg.Printf( _( "Cannot copy file '%s'." ), aDestPath );
345 aErrors += msg;
346 }
347}
348
349
350static void traverseSEXPR( SEXPR::SEXPR* aNode, const std::function<void( SEXPR::SEXPR* )>& aVisitor )
351{
352 aVisitor( aNode );
353
354 if( aNode->IsList() )
355 {
356 for( unsigned i = 0; i < aNode->GetNumberOfChildren(); i++ )
357 traverseSEXPR( aNode->GetChild( i ), aVisitor );
358 }
359}
360
361
362void CopySexprFile( const wxString& aSrcPath, const wxString& aDestPath,
363 std::function<bool( const std::string& token, wxString& value )> aCallback,
364 wxString& aErrors )
365{
366 bool success = false;
367
368 try
369 {
370 SEXPR::PARSER parser;
371 std::unique_ptr<SEXPR::SEXPR> sexpr( parser.ParseFromFile( TO_UTF8( aSrcPath ) ) );
372
373 traverseSEXPR( sexpr.get(),
374 [&]( SEXPR::SEXPR* node )
375 {
376 if( node->IsList() && node->GetNumberOfChildren() > 1 && node->GetChild( 0 )->IsSymbol() )
377 {
378 std::string token = node->GetChild( 0 )->GetSymbol();
379 SEXPR::SEXPR_STRING* pathNode = dynamic_cast<SEXPR::SEXPR_STRING*>( node->GetChild( 1 ) );
380 SEXPR::SEXPR_SYMBOL* symNode = dynamic_cast<SEXPR::SEXPR_SYMBOL*>( node->GetChild( 1 ) );
381 wxString path;
382
383 if( pathNode )
384 path = pathNode->m_value;
385 else if( symNode )
386 path = symNode->m_value;
387
388 if( aCallback( token, path ) )
389 {
390 if( pathNode )
391 pathNode->m_value = path;
392 else if( symNode )
393 symNode->m_value = path;
394 }
395 }
396 } );
397
398
399 // Pass through the pretifier to ensure format is the same as when a file is saved by a frame
400 PRETTIFIED_FILE_OUTPUTFORMATTER formatter( aDestPath );
401
402 // Format as a string to prevent format string attacks
403 formatter.Print( "%s", sexpr->AsString( 0 ).c_str() );
404
405 success = formatter.Finish();
406 }
407 catch( ... )
408 {
409 success = false;
410 }
411
412 if( !success )
413 {
414 wxString msg;
415
416 if( !aErrors.empty() )
417 aErrors += wxS( "\n" );
418
419 msg.Printf( _( "Cannot copy file '%s'." ), aDestPath );
420 aErrors += msg;
421 }
422}
423
424
425wxString QuoteFullPath( wxFileName& fn, wxPathFormat format )
426{
427 return wxT( "\"" ) + fn.GetFullPath( format ) + wxT( "\"" );
428}
429
430
431bool RmDirRecursive( const wxString& aFileName, wxString* aErrors )
432{
433 namespace fs = std::filesystem;
434
435 std::string rmDir = aFileName.ToStdString();
436
437 if( rmDir.length() < 3 )
438 {
439 if( aErrors )
440 *aErrors = _( "Invalid directory name, cannot remove root" );
441
442 return false;
443 }
444
445 if( !fs::exists( rmDir ) )
446 {
447 if( aErrors )
448 *aErrors = wxString::Format( _( "Directory '%s' does not exist" ), aFileName );
449
450 return false;
451 }
452
453 fs::path path( rmDir );
454
455 if( !fs::is_directory( path ) )
456 {
457 if( aErrors )
458 *aErrors = wxString::Format( _( "'%s' is not a directory" ), aFileName );
459
460 return false;
461 }
462
463 try
464 {
465 fs::remove_all( path );
466 }
467 catch( const fs::filesystem_error& e )
468 {
469 if( aErrors )
470 *aErrors = wxString::Format( _( "Error removing directory '%s': %s" ), aFileName, e.what() );
471
472 return false;
473 }
474
475 return true;
476}
477
478
479bool CopyDirectory( const wxString& aSourceDir, const wxString& aDestDir,
480 const std::vector<wxString>& aPathsWithOverwriteDisallowed, wxString& aErrors )
481{
482 wxDir dir( aSourceDir );
483
484 if( !dir.IsOpened() )
485 {
486 aErrors += wxString::Format( _( "Could not open source directory: %s" ), aSourceDir );
487 aErrors += wxT( "\n" );
488 return false;
489 }
490
491 if( !wxFileName::Mkdir( aDestDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
492 {
493 aErrors += wxString::Format( _( "Could not create destination directory: %s" ), aDestDir );
494 aErrors += wxT( "\n" );
495 return false;
496 }
497
498 wxString filename;
499 bool cont = dir.GetFirst( &filename );
500
501 while( cont )
502 {
503 wxString sourcePath = dir.GetNameWithSep() + filename;
504 wxString destPath = aDestDir + wxFileName::GetPathSeparator() + filename;
505
506 if( wxFileName::DirExists( sourcePath ) )
507 {
508 // Recursively copy subdirectories
509 if( !CopyDirectory( sourcePath, destPath, aPathsWithOverwriteDisallowed, aErrors ) )
510 return false;
511 }
512 else
513 {
514 // Copy files
515 if( alg::contains( aPathsWithOverwriteDisallowed, sourcePath ) && wxFileExists( destPath ) )
516 {
517 // Presumably user does not want an error on a no-overwrite condition....
518 }
519 else if( !wxCopyFile( sourcePath, destPath ) )
520 {
521 aErrors += wxString::Format( _( "Could not copy file: %s to %s" ), sourcePath, destPath );
522 return false;
523 }
524 }
525
526 cont = dir.GetNext( &filename );
527 }
528
529 return true;
530}
531
532
533bool CopyFilesOrDirectory( const wxString& aSourcePath, const wxString& aDestDir, bool aAllowOverwrites,
534 wxString& aErrors, std::vector<wxString>& aPathsWritten )
535{
536 // Parse source path and determine if it's a directory
537 wxFileName sourceFn( aSourcePath );
538 wxString sourcePath = sourceFn.GetFullPath();
539 bool isSourceDirectory = wxFileName::DirExists( sourcePath );
540 wxString baseDestDir = aDestDir;
541
542 auto performCopy =
543 [&]( const wxString& src, const wxString& dest ) -> bool
544 {
545 if( wxCopyFile( src, dest, aAllowOverwrites ) )
546 {
547 aPathsWritten.push_back( dest );
548 return true;
549 }
550
551 aErrors += wxString::Format( _( "Could not copy file: %s to %s" ), src, dest );
552 aErrors += wxT( "\n" );
553 return false;
554 };
555
556 auto processEntries =
557 [&]( const wxString& srcDir, const wxString& pattern, const wxString& destDir ) -> bool
558 {
559 wxDir dir( srcDir );
560
561 if( !dir.IsOpened() )
562 {
563 aErrors += wxString::Format( _( "Could not open source directory: %s" ), srcDir );
564 aErrors += wxT( "\n" );
565 return false;
566 }
567
568 wxString filename;
569 bool success = true;
570
571 // Find all entries matching pattern (files + directories + hidden items)
572 bool cont = dir.GetFirst( &filename, pattern, wxDIR_FILES | wxDIR_DIRS | wxDIR_HIDDEN );
573
574 while( cont )
575 {
576 const wxString entrySrc = srcDir + wxFileName::GetPathSeparator() + filename;
577 const wxString entryDest = destDir + wxFileName::GetPathSeparator() + filename;
578
579 if( !filename.Matches( wxT( "~*.lck" ) ) && !filename.Matches( wxT( "*.lck" ) ) )
580 {
581 if( wxFileName::DirExists( entrySrc ) )
582 {
583 // Recursively process subdirectories
584 if( !CopyFilesOrDirectory( entrySrc, destDir, aAllowOverwrites, aErrors, aPathsWritten ) )
585 {
586 aErrors += wxString::Format( _( "Could not copy directory: %s to %s" ),
587 entrySrc, entryDest );
588 aErrors += wxT( "\n" );
589
590 success = false;
591 }
592 }
593 else
594 {
595 // Copy individual files
596 if( !performCopy( entrySrc, entryDest ) )
597 {
598 success = false;
599 }
600 }
601 }
602
603 cont = dir.GetNext( &filename );
604 }
605
606 return success;
607 };
608
609 // If copying a directory, append its name to destination path
610 if( isSourceDirectory )
611 {
612 wxString sourceDirName = sourceFn.GetFullName();
613 baseDestDir = wxFileName( aDestDir, sourceDirName ).GetFullPath();
614 }
615
616 // Create destination directory hierarchy
617 if( !wxFileName::Mkdir( baseDestDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
618 {
619 aErrors += wxString::Format( _( "Could not create destination directory: %s" ), baseDestDir );
620 aErrors += wxT( "\n" );
621
622 return false;
623 }
624
625 // Execute appropriate copy operation based on source type
626 if( !isSourceDirectory )
627 {
628 const wxString fileName = sourceFn.GetFullName();
629
630 // Handle wildcard patterns in filenames
631 if( fileName.Contains( '*' ) || fileName.Contains( '?' ) )
632 {
633 const wxString dirPath = sourceFn.GetPath();
634
635 if( !wxFileName::DirExists( dirPath ) )
636 {
637 aErrors += wxString::Format( _( "Source directory does not exist: %s" ), dirPath );
638 aErrors += wxT( "\n" );
639
640 return false;
641 }
642
643 // Process all matching files in source directory
644 return processEntries( dirPath, fileName, baseDestDir );
645 }
646
647 // Single file copy operation
648 return performCopy( sourcePath, wxFileName( baseDestDir, fileName ).GetFullPath() );
649 }
650
651 // Full directory copy operation
652 return processEntries( sourcePath, wxEmptyString, baseDestDir );
653}
654
655
656bool AddDirectoryToZip( wxZipOutputStream& aZip, const wxString& aSourceDir, wxString& aErrors,
657 const wxString& aParentDir )
658{
659 wxDir dir( aSourceDir );
660
661 if( !dir.IsOpened() )
662 {
663 aErrors += wxString::Format( _( "Could not open source directory: %s" ), aSourceDir );
664 aErrors += "\n";
665 return false;
666 }
667
668 wxString filename;
669 bool cont = dir.GetFirst( &filename );
670
671 while( cont )
672 {
673 wxString sourcePath = aSourceDir + wxFileName::GetPathSeparator() + filename;
674 wxString zipPath = aParentDir + filename;
675
676 if( wxFileName::DirExists( sourcePath ) )
677 {
678 // Add directory entry to the ZIP file
679 aZip.PutNextDirEntry( zipPath + "/" );
680
681 // Recursively add subdirectories
682 if( !AddDirectoryToZip( aZip, sourcePath, aErrors, zipPath + "/" ) )
683 return false;
684 }
685 else
686 {
687 // Add file entry to the ZIP file
688 aZip.PutNextEntry( zipPath );
689 wxFFileInputStream fileStream( sourcePath );
690
691 if( !fileStream.IsOk() )
692 {
693 aErrors += wxString::Format( _( "Could not read file: %s" ), sourcePath );
694 return false;
695 }
696
697 aZip.Write( fileStream );
698 }
699
700 cont = dir.GetNext( &filename );
701 }
702
703 return true;
704}
705
706
707namespace
708{
709
710std::filesystem::path toFsPath( const wxString& aPath )
711{
712#ifdef __WXMSW__
713 return std::filesystem::path( std::wstring( aPath.wc_str() ) );
714#else
715 return std::filesystem::path( aPath.utf8_string() );
716#endif
717}
718
719
720// Best-effort canonicalisation. Empty path means "couldn't resolve"
721// (broken symlink, ELOOP, etc.); callers treat that as "skip rather than
722// risk recursing".
723std::filesystem::path canonicalPath( const std::filesystem::path& aPath )
724{
725 std::error_code ec;
726 std::filesystem::path canon = std::filesystem::weakly_canonical( aPath, ec );
727
728 return ec ? std::filesystem::path() : canon;
729}
730
731
732// True if @p aAncestor is equal to or an ancestor of @p aDescendant. Both
733// must already be canonical so that "/a/b" and "/a/b/c" share a prefix
734// component-wise.
735bool isAncestorOrSame( const std::filesystem::path& aAncestor,
736 const std::filesystem::path& aDescendant )
737{
738 auto a = aAncestor.begin();
739 auto d = aDescendant.begin();
740
741 for( ; a != aAncestor.end() && d != aDescendant.end(); ++a, ++d )
742 {
743 if( *a != *d )
744 return false;
745 }
746
747 return a == aAncestor.end();
748}
749
750
751// Shared traverser for CollectFilesLoopSafe / CollectSubdirsLoopSafe. Records
752// files, directories, or both into @p aOutput while deduplicating visited
753// directories by canonical path so recursive symlinks terminate.
754class LOOP_SAFE_COLLECTOR : public wxDirTraverser
755{
756public:
757 LOOP_SAFE_COLLECTOR( wxArrayString& aOutput, const wxString& aRoot, bool aCollectFiles,
758 bool aCollectDirs ) :
759 m_output( aOutput ),
760 m_root( canonicalPath( toFsPath( aRoot ) ) ),
761 m_collectFiles( aCollectFiles ),
762 m_collectDirs( aCollectDirs )
763 {
764 m_visited.reserve( 256 );
765
766 if( !m_root.empty() )
767 m_visited.insert( m_root.generic_string() );
768 }
769
770 wxDirTraverseResult OnFile( const wxString& aFilename ) override
771 {
772 if( m_collectFiles )
773 m_output.Add( aFilename );
774
775 return wxDIR_CONTINUE;
776 }
777
778 wxDirTraverseResult OnDir( const wxString& aDirname ) override
779 {
780 const std::filesystem::path raw = toFsPath( aDirname );
781
782 // Fast path: a real (non-symlink) subdir can't introduce a cycle by
783 // itself, so skip the per-component weakly_canonical and use the
784 // string-only lexically_normal as the dedup key. Cold-cache walks
785 // of large model libraries pay one lstat per dir instead of one per
786 // path component.
787 std::error_code ec;
788 const bool isLink = std::filesystem::is_symlink( raw, ec );
789
790 std::filesystem::path key;
791
792 if( isLink && !ec )
793 {
794 const std::filesystem::path canon = canonicalPath( raw );
795
796 if( canon.empty() )
797 return wxDIR_IGNORE;
798
799 // Refuse to escape the scan tree. Stops Wine 'dosdevices/z: -> /'
800 // and similar root-escape symlinks from walking the whole disk
801 // before the visited-set catches the eventual re-entry.
802 if( !m_root.empty() && isAncestorOrSame( canon, m_root ) )
803 return wxDIR_IGNORE;
804
805 key = canon;
806 }
807 else
808 {
809 key = raw.lexically_normal();
810 }
811
812 if( !m_visited.insert( key.generic_string() ).second )
813 return wxDIR_IGNORE;
814
815 if( m_collectDirs )
816 m_output.Add( aDirname );
817
818 return wxDIR_CONTINUE;
819 }
820
821private:
822 wxArrayString& m_output;
823 std::filesystem::path m_root;
824 bool m_collectFiles;
825 bool m_collectDirs;
826 std::unordered_set<std::string> m_visited;
827};
828
829
830void traverseLoopSafe( const wxString& aRoot, wxArrayString& aOutput, bool aCollectFiles,
831 bool aCollectDirs, const wxString& aFileSpec, int aFlags )
832{
833 wxDir dir( aRoot );
834
835 if( !dir.IsOpened() )
836 return;
837
838 LOOP_SAFE_COLLECTOR collector( aOutput, aRoot, aCollectFiles, aCollectDirs );
839 dir.Traverse( collector, aFileSpec, aFlags );
840}
841
842} // namespace
843
844
845void CollectFilesLoopSafe( const wxString& aRoot, wxArrayString& aFiles, const wxString& aFileSpec,
846 int aFlags )
847{
848 // Force wxDIR_FILES so files are reported and wxDIR_DIRS so Traverse descends
849 // into subdirectories; the collector keeps directories out of the file list
850 // and breaks loops. aFlags carries the caller's wxDIR_HIDDEN choice.
851 traverseLoopSafe( aRoot, aFiles, true, false, aFileSpec, aFlags | wxDIR_FILES | wxDIR_DIRS );
852}
853
854
855void CollectSubdirsLoopSafe( const wxString& aRoot, wxArrayString& aDirs, int aFlags )
856{
857 traverseLoopSafe( aRoot, aDirs, false, true, wxEmptyString, aFlags | wxDIR_DIRS );
858}
constexpr std::size_t arrayDim(T const (&)[N]) noexcept
Returns # of elements in an array.
Definition arraydim.h:27
int PRINTF_FUNC_N Print(int nestLevel, const char *fmt,...)
Format and write text to the output stream.
Definition richio.cpp:422
virtual const wxString & GetKicadEnvVariable() const
Definition pgm_base.h:171
virtual void ReadPdfBrowserInfos()
Read the PDF browser choice from the common configuration.
Definition pgm_base.cpp:885
virtual const wxString & GetPdfBrowserName() const
Definition pgm_base.h:177
virtual const wxString & GetExecutablePath() const
Definition pgm_base.cpp:879
bool Finish() override
Runs prettification over the buffered bytes, writes them to the sibling temp file,...
Definition richio.cpp:696
std::unique_ptr< SEXPR > ParseFromFile(const std::string &aFilename)
size_t GetNumberOfChildren() const
Definition sexpr.cpp:72
bool IsList() const
Definition sexpr.h:49
SEXPR * GetChild(size_t aIndex) const
Definition sexpr.cpp:50
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
This file is part of the common library.
#define _(s)
wxString FindKicadFile(const wxString &shortname)
Search the executable file shortname in KiCad binary path and return full file name if found or short...
Definition gestfich.cpp:61
wxString QuoteFullPath(wxFileName &fn, wxPathFormat format)
Quote return value of wxFileName::GetFullPath().
Definition gestfich.cpp:425
int ExecuteCommandThroughShell(const wxString &aCommand, wxProcess *aProcess)
Run a user-supplied command line through the platform shell so glob expansion, pipes,...
Definition gestfich.cpp:271
void CopySexprFile(const wxString &aSrcPath, const wxString &aDestPath, std::function< bool(const std::string &token, wxString &value)> aCallback, wxString &aErrors)
Definition gestfich.cpp:362
bool OpenPDF(const wxString &file)
Run the PDF viewer and display a PDF file.
Definition gestfich.cpp:299
void KiCopyFile(const wxString &aSrcPath, const wxString &aDestPath, wxString &aErrors)
Definition gestfich.cpp:335
int ExecuteFile(const wxString &aEditorName, const wxString &aFileName, wxProcess *aCallback, bool aFileForKicad)
Call the executable file aEditorName with the parameter aFileName.
Definition gestfich.cpp:160
bool RmDirRecursive(const wxString &aFileName, wxString *aErrors)
Remove the directory aDirName and all its contents including subdirectories and their files.
Definition gestfich.cpp:431
void QuoteString(wxString &string)
Add un " to the start and the end of string (if not already done).
Definition gestfich.cpp:51
bool CopyFilesOrDirectory(const wxString &aSourcePath, const wxString &aDestDir, bool aAllowOverwrites, wxString &aErrors, std::vector< wxString > &aPathsWritten)
Definition gestfich.cpp:533
void CollectSubdirsLoopSafe(const wxString &aRoot, wxArrayString &aDirs, int aFlags)
Recursively collect every subdirectory under aRoot using the same loop detection as CollectFilesLoopS...
Definition gestfich.cpp:855
void CollectFilesLoopSafe(const wxString &aRoot, wxArrayString &aFiles, const wxString &aFileSpec, int aFlags)
Recursively collect every file under aRoot, deduplicating subdirectories by their resolved path.
Definition gestfich.cpp:845
static void traverseSEXPR(SEXPR::SEXPR *aNode, const std::function< void(SEXPR::SEXPR *)> &aVisitor)
Definition gestfich.cpp:350
bool CopyDirectory(const wxString &aSourceDir, const wxString &aDestDir, const std::vector< wxString > &aPathsWithOverwriteDisallowed, wxString &aErrors)
Copy a directory and its contents to another directory.
Definition gestfich.cpp:479
bool AddDirectoryToZip(wxZipOutputStream &aZip, const wxString &aSourceDir, wxString &aErrors, const wxString &aParentDir)
Add a directory and its contents to a zip file.
Definition gestfich.cpp:656
bool LaunchExternal(const wxString &aPath)
Launches the given file or folder in the host OS.
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition kicad_algo.h:96
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
std::string path