31#include <wx/mimetype.h>
40#include "wx/tokenzr.h"
45#include <wx/wfstream.h>
47#include <wx/zipstrm.h>
51#include <system_error>
52#include <unordered_set>
57 if( !
string.StartsWith( wxT(
"\"" ) ) )
59 string.Prepend ( wxT(
"\"" ) );
60 string.Append ( wxT(
"\"" ) );
74 if( wxFileExists( fullFileName ) )
77 if( wxGetEnv( wxT(
"KICAD_RUN_FROM_BUILD_DIR" ),
nullptr ) )
79 wxFileName buildDir(
Pgm().GetExecutablePath(), shortname );
82 if( !buildDir.GetDirs().IsEmpty()
83 && buildDir.GetDirs().Last().Lower().EndsWith( wxT(
".app" ) ) )
85 buildDir.RemoveLastDir();
88 buildDir.RemoveLastDir();
90 buildDir.AppendDir( shortname );
92 buildDir.AppendDir( shortname.BeforeLast(
'.' ) );
95 if( buildDir.GetDirs().Last() ==
"pl_editor" )
97 buildDir.RemoveLastDir();
98 buildDir.AppendDir(
"pagelayout_editor" );
102 buildDir.AppendDir( shortname + wxT(
".app" ) );
103 buildDir.AppendDir( wxT(
"Contents" ) );
104 buildDir.AppendDir( wxT(
"MacOS" ) );
107 if( wxFileExists( buildDir.GetFullPath() ) )
108 return buildDir.GetFullPath();
113 if(
Pgm().IsKicadEnvVariableDefined() )
117 if( wxFileExists( fullFileName ) )
121#if defined( __WINDOWS__ )
128 const static wxChar* possibilities[] = {
129#if defined( __WXMAC__ )
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/" ),
139 wxT(
"/usr/local/bin/" ),
140 wxT(
"/usr/local/kicad/bin/" ),
145 for(
unsigned i=0; i<
arrayDim(possibilities); ++i )
148 fullFileName = possibilities[i] + shortname;
154 if( wxFileExists( fullFileName ) )
164int ExecuteFile(
const wxString& aEditorName,
const wxString& aFileName, wxProcess* aCallback,
167 wxString fullEditorName;
168 std::vector<wxString> params;
172 bool inSingleQuotes =
false;
173 bool inDoubleQuotes =
false;
178 if( !param.IsEmpty() )
180 params.push_back( param );
185 for( wxUniChar ch : aEditorName )
192 inSingleQuotes =
false;
200 else if( inDoubleQuotes )
205 inDoubleQuotes =
false;
212 else if( ch ==
'\'' )
215 inSingleQuotes =
true;
220 inDoubleQuotes =
true;
237 fullEditorName = params[0];
239 params.erase( params.begin() );
245 fullEditorName = aEditorName;
248 if( wxFileExists( fullEditorName ) )
250 std::vector<const wchar_t*> args;
252 args.emplace_back( fullEditorName.wc_str() );
254 if( !params.empty() )
256 for(
const wxString& p : params )
257 args.emplace_back( p.wc_str() );
260 if( !aFileName.IsEmpty() )
261 args.emplace_back( aFileName.wc_str() );
263 args.emplace_back(
nullptr );
265 return wxExecute(
const_cast<wchar_t**
>( args.data() ), wxEXEC_ASYNC, aCallback );
269 msg.Printf(
_(
"Command '%s' could not be found." ), fullEditorName );
278 wxString filename = file;
282 if(
Pgm().UseSystemPdfBrowser() )
286 msg.Printf(
_(
"Unable to find a PDF viewer for '%s'." ), filename );
293 const wchar_t* args[3];
296 args[1] = filename.wc_str();
299 if( wxExecute(
const_cast<wchar_t**
>( args ) ) == -1 )
301 msg.Printf(
_(
"Problem while running the PDF viewer '%s'." ), args[0] );
311void KiCopyFile(
const wxString& aSrcPath,
const wxString& aDestPath, wxString& aErrors )
313 if( !wxCopyFile( aSrcPath, aDestPath ) )
317 if( !aErrors.IsEmpty() )
320 msg.Printf(
_(
"Cannot copy file '%s'." ), aDestPath );
339 std::function<
bool(
const std::string& token, wxString& value )> aCallback,
342 bool success =
false;
352 if( node->IsList() && node->GetNumberOfChildren() > 1 && node->GetChild( 0 )->IsSymbol() )
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 ) );
360 path = pathNode->m_value;
362 path = symNode->m_value;
364 if( aCallback( token, path ) )
367 pathNode->m_value = path;
369 symNode->m_value = path;
379 formatter.
Print(
"%s", sexpr->AsString( 0 ).c_str() );
381 success = formatter.
Finish();
392 if( !aErrors.empty() )
393 aErrors += wxS(
"\n" );
395 msg.Printf(
_(
"Cannot copy file '%s'." ), aDestPath );
403 return wxT(
"\"" ) + fn.GetFullPath( format ) + wxT(
"\"" );
409 namespace fs = std::filesystem;
411 std::string rmDir = aFileName.ToStdString();
413 if( rmDir.length() < 3 )
416 *aErrors =
_(
"Invalid directory name, cannot remove root" );
421 if( !fs::exists( rmDir ) )
424 *aErrors = wxString::Format(
_(
"Directory '%s' does not exist" ), aFileName );
429 fs::path
path( rmDir );
431 if( !fs::is_directory(
path ) )
434 *aErrors = wxString::Format(
_(
"'%s' is not a directory" ), aFileName );
441 fs::remove_all(
path );
443 catch(
const fs::filesystem_error& e )
446 *aErrors = wxString::Format(
_(
"Error removing directory '%s': %s" ), aFileName, e.what() );
456 const std::vector<wxString>& aPathsWithOverwriteDisallowed, wxString& aErrors )
458 wxDir dir( aSourceDir );
460 if( !dir.IsOpened() )
462 aErrors += wxString::Format(
_(
"Could not open source directory: %s" ), aSourceDir );
463 aErrors += wxT(
"\n" );
467 if( !wxFileName::Mkdir( aDestDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
469 aErrors += wxString::Format(
_(
"Could not create destination directory: %s" ), aDestDir );
470 aErrors += wxT(
"\n" );
475 bool cont = dir.GetFirst( &filename );
479 wxString sourcePath = dir.GetNameWithSep() + filename;
480 wxString destPath = aDestDir + wxFileName::GetPathSeparator() + filename;
482 if( wxFileName::DirExists( sourcePath ) )
485 if( !
CopyDirectory( sourcePath, destPath, aPathsWithOverwriteDisallowed, aErrors ) )
491 if(
alg::contains( aPathsWithOverwriteDisallowed, sourcePath ) && wxFileExists( destPath ) )
495 else if( !wxCopyFile( sourcePath, destPath ) )
497 aErrors += wxString::Format(
_(
"Could not copy file: %s to %s" ), sourcePath, destPath );
502 cont = dir.GetNext( &filename );
510 wxString& aErrors, std::vector<wxString>& aPathsWritten )
513 wxFileName sourceFn( aSourcePath );
514 wxString sourcePath = sourceFn.GetFullPath();
515 bool isSourceDirectory = wxFileName::DirExists( sourcePath );
516 wxString baseDestDir = aDestDir;
519 [&](
const wxString& src,
const wxString& dest ) ->
bool
521 if( wxCopyFile( src, dest, aAllowOverwrites ) )
523 aPathsWritten.push_back( dest );
527 aErrors += wxString::Format(
_(
"Could not copy file: %s to %s" ), src, dest );
528 aErrors += wxT(
"\n" );
532 auto processEntries =
533 [&](
const wxString& srcDir,
const wxString& pattern,
const wxString& destDir ) ->
bool
537 if( !dir.IsOpened() )
539 aErrors += wxString::Format(
_(
"Could not open source directory: %s" ), srcDir );
540 aErrors += wxT(
"\n" );
548 bool cont = dir.GetFirst( &filename, pattern, wxDIR_FILES | wxDIR_DIRS | wxDIR_HIDDEN );
552 const wxString entrySrc = srcDir + wxFileName::GetPathSeparator() + filename;
553 const wxString entryDest = destDir + wxFileName::GetPathSeparator() + filename;
555 if( !filename.Matches( wxT(
"~*.lck" ) ) && !filename.Matches( wxT(
"*.lck" ) ) )
557 if( wxFileName::DirExists( entrySrc ) )
562 aErrors += wxString::Format(
_(
"Could not copy directory: %s to %s" ),
563 entrySrc, entryDest );
564 aErrors += wxT(
"\n" );
572 if( !performCopy( entrySrc, entryDest ) )
579 cont = dir.GetNext( &filename );
586 if( isSourceDirectory )
588 wxString sourceDirName = sourceFn.GetFullName();
589 baseDestDir = wxFileName( aDestDir, sourceDirName ).GetFullPath();
593 if( !wxFileName::Mkdir( baseDestDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
595 aErrors += wxString::Format(
_(
"Could not create destination directory: %s" ), baseDestDir );
596 aErrors += wxT(
"\n" );
602 if( !isSourceDirectory )
604 const wxString fileName = sourceFn.GetFullName();
607 if( fileName.Contains(
'*' ) || fileName.Contains(
'?' ) )
609 const wxString dirPath = sourceFn.GetPath();
611 if( !wxFileName::DirExists( dirPath ) )
613 aErrors += wxString::Format(
_(
"Source directory does not exist: %s" ), dirPath );
614 aErrors += wxT(
"\n" );
620 return processEntries( dirPath, fileName, baseDestDir );
624 return performCopy( sourcePath, wxFileName( baseDestDir, fileName ).GetFullPath() );
628 return processEntries( sourcePath, wxEmptyString, baseDestDir );
633 const wxString& aParentDir )
635 wxDir dir( aSourceDir );
637 if( !dir.IsOpened() )
639 aErrors += wxString::Format(
_(
"Could not open source directory: %s" ), aSourceDir );
645 bool cont = dir.GetFirst( &filename );
649 wxString sourcePath = aSourceDir + wxFileName::GetPathSeparator() + filename;
650 wxString zipPath = aParentDir + filename;
652 if( wxFileName::DirExists( sourcePath ) )
655 aZip.PutNextDirEntry( zipPath +
"/" );
664 aZip.PutNextEntry( zipPath );
665 wxFFileInputStream fileStream( sourcePath );
667 if( !fileStream.IsOk() )
669 aErrors += wxString::Format(
_(
"Could not read file: %s" ), sourcePath );
673 aZip.Write( fileStream );
676 cont = dir.GetNext( &filename );
686std::filesystem::path toFsPath(
const wxString& aPath )
689 return std::filesystem::path( std::wstring( aPath.wc_str() ) );
691 return std::filesystem::path( aPath.utf8_string() );
699std::filesystem::path canonicalPath(
const std::filesystem::path& aPath )
702 std::filesystem::path canon = std::filesystem::weakly_canonical( aPath, ec );
704 return ec ? std::filesystem::path() : canon;
711bool isAncestorOrSame(
const std::filesystem::path& aAncestor,
712 const std::filesystem::path& aDescendant )
714 auto a = aAncestor.begin();
715 auto d = aDescendant.begin();
717 for( ; a != aAncestor.end() && d != aDescendant.end(); ++a, ++d )
723 return a == aAncestor.end();
730class LOOP_SAFE_COLLECTOR :
public wxDirTraverser
733 LOOP_SAFE_COLLECTOR( wxArrayString& aOutput,
const wxString& aRoot,
bool aCollectFiles,
734 bool aCollectDirs ) :
736 m_root( canonicalPath( toFsPath( aRoot ) ) ),
737 m_collectFiles( aCollectFiles ),
738 m_collectDirs( aCollectDirs )
740 m_visited.reserve( 256 );
742 if( !m_root.empty() )
743 m_visited.insert( m_root.generic_string() );
746 wxDirTraverseResult OnFile(
const wxString& aFilename )
override
749 m_output.Add( aFilename );
751 return wxDIR_CONTINUE;
754 wxDirTraverseResult OnDir(
const wxString& aDirname )
override
756 const std::filesystem::path raw = toFsPath( aDirname );
764 const bool isLink = std::filesystem::is_symlink( raw, ec );
766 std::filesystem::path key;
770 const std::filesystem::path canon = canonicalPath( raw );
778 if( !m_root.empty() && isAncestorOrSame( canon, m_root ) )
785 key = raw.lexically_normal();
788 if( !m_visited.insert( key.generic_string() ).second )
792 m_output.Add( aDirname );
794 return wxDIR_CONTINUE;
798 wxArrayString& m_output;
799 std::filesystem::path m_root;
802 std::unordered_set<std::string> m_visited;
806void traverseLoopSafe(
const wxString& aRoot, wxArrayString& aOutput,
bool aCollectFiles,
807 bool aCollectDirs,
const wxString& aFileSpec,
int aFlags )
811 if( !dir.IsOpened() )
814 LOOP_SAFE_COLLECTOR collector( aOutput, aRoot, aCollectFiles, aCollectDirs );
815 dir.Traverse( collector, aFileSpec, aFlags );
827 traverseLoopSafe( aRoot, aFiles,
true,
false, aFileSpec, aFlags | wxDIR_FILES | wxDIR_DIRS );
833 traverseLoopSafe( aRoot, aDirs,
false,
true, wxEmptyString, aFlags | wxDIR_DIRS );
constexpr std::size_t arrayDim(T const (&)[N]) noexcept
Returns # of elements in an array.
virtual const wxString & GetKicadEnvVariable() const
virtual void ReadPdfBrowserInfos()
Read the PDF browser choice from the common configuration.
virtual const wxString & GetPdfBrowserName() const
virtual const wxString & GetExecutablePath() const
std::unique_ptr< SEXPR > ParseFromFile(const std::string &aFilename)
size_t GetNumberOfChildren() const
SEXPR * GetChild(size_t aIndex) const
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
This file is part of the common library.
wxString FindKicadFile(const wxString &shortname)
Search the executable file shortname in KiCad binary path and return full file name if found or short...
wxString QuoteFullPath(wxFileName &fn, wxPathFormat format)
Quote return value of wxFileName::GetFullPath().
void CopySexprFile(const wxString &aSrcPath, const wxString &aDestPath, std::function< bool(const std::string &token, wxString &value)> aCallback, wxString &aErrors)
bool OpenPDF(const wxString &file)
Run the PDF viewer and display a PDF file.
void KiCopyFile(const wxString &aSrcPath, const wxString &aDestPath, wxString &aErrors)
int ExecuteFile(const wxString &aEditorName, const wxString &aFileName, wxProcess *aCallback, bool aFileForKicad)
Call the executable file aEditorName with the parameter aFileName.
bool RmDirRecursive(const wxString &aFileName, wxString *aErrors)
Remove the directory aDirName and all its contents including subdirectories and their files.
void QuoteString(wxString &string)
Add un " to the start and the end of string (if not already done).
bool CopyFilesOrDirectory(const wxString &aSourcePath, const wxString &aDestDir, bool aAllowOverwrites, wxString &aErrors, std::vector< wxString > &aPathsWritten)
void CollectSubdirsLoopSafe(const wxString &aRoot, wxArrayString &aDirs, int aFlags)
Recursively collect every subdirectory under aRoot using the same loop detection as CollectFilesLoopS...
void CollectFilesLoopSafe(const wxString &aRoot, wxArrayString &aFiles, const wxString &aFileSpec, int aFlags)
Recursively collect every file under aRoot, deduplicating subdirectories by their resolved path.
static void traverseSEXPR(SEXPR::SEXPR *aNode, const std::function< void(SEXPR::SEXPR *)> &aVisitor)
bool CopyDirectory(const wxString &aSourceDir, const wxString &aDestDir, const std::vector< wxString > &aPathsWithOverwriteDisallowed, wxString &aErrors)
Copy a directory and its contents to another directory.
bool AddDirectoryToZip(wxZipOutputStream &aZip, const wxString &aSourceDir, wxString &aErrors, const wxString &aParentDir)
Add a directory and its contents to a zip file.
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.
PGM_BASE & Pgm()
The global program "get" accessor.
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.