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