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 <sexpr/sexpr.h>
42#include <sexpr/sexpr_parser.h>
43
44#include <wx/wfstream.h>
45#include <wx/fs_zip.h>
46#include <wx/zipstrm.h>
47
48#include <filesystem>
49
50void QuoteString( wxString& string )
51{
52 if( !string.StartsWith( wxT( "\"" ) ) )
53 {
54 string.Prepend ( wxT( "\"" ) );
55 string.Append ( wxT( "\"" ) );
56 }
57}
58
59
60wxString FindKicadFile( const wxString& shortname )
61{
62 // Test the presence of the file in the directory shortname of
63 // the KiCad binary path.
64#ifndef __WXMAC__
65 wxString fullFileName = Pgm().GetExecutablePath() + shortname;
66#else
67 wxString fullFileName = Pgm().GetExecutablePath() + wxT( "Contents/MacOS/" ) + shortname;
68#endif
69 if( wxFileExists( fullFileName ) )
70 return fullFileName;
71
72 if( wxGetEnv( wxT( "KICAD_RUN_FROM_BUILD_DIR" ), nullptr ) )
73 {
74 wxFileName buildDir( Pgm().GetExecutablePath(), shortname );
75 buildDir.RemoveLastDir();
76#ifndef __WXMSW__
77 buildDir.AppendDir( shortname );
78#else
79 buildDir.AppendDir( shortname.BeforeLast( '.' ) );
80#endif
81
82 if( buildDir.GetDirs().Last() == "pl_editor" )
83 {
84 buildDir.RemoveLastDir();
85 buildDir.AppendDir( "pagelayout_editor" );
86 }
87
88 if( wxFileExists( buildDir.GetFullPath() ) )
89 return buildDir.GetFullPath();
90 }
91
92 // Test the presence of the file in the directory shortname
93 // defined by the environment variable KiCad.
94 if( Pgm().IsKicadEnvVariableDefined() )
95 {
96 fullFileName = Pgm().GetKicadEnvVariable() + shortname;
97
98 if( wxFileExists( fullFileName ) )
99 return fullFileName;
100 }
101
102#if defined( __WINDOWS__ )
103 // KiCad can be installed highly portably on Windows, anywhere and concurrently
104 // either the "kicad file" is immediately adjacent to the exe or it's not a valid install
105 return shortname;
106#else
107
108 // Path list for KiCad binary files
109 const static wxChar* possibilities[] = {
110#if defined( __WXMAC__ )
111 // all internal paths are relative to main bundle kicad.app
112 wxT( "Contents/Applications/pcbnew.app/Contents/MacOS/" ),
113 wxT( "Contents/Applications/eeschema.app/Contents/MacOS/" ),
114 wxT( "Contents/Applications/gerbview.app/Contents/MacOS/" ),
115 wxT( "Contents/Applications/bitmap2component.app/Contents/MacOS/" ),
116 wxT( "Contents/Applications/pcb_calculator.app/Contents/MacOS/" ),
117 wxT( "Contents/Applications/pl_editor.app/Contents/MacOS/" ),
118#else
119 wxT( "/usr/bin/" ),
120 wxT( "/usr/local/bin/" ),
121 wxT( "/usr/local/kicad/bin/" ),
122#endif
123 };
124
125 // find binary file from possibilities list:
126 for( unsigned i=0; i<arrayDim(possibilities); ++i )
127 {
128#ifndef __WXMAC__
129 fullFileName = possibilities[i] + shortname;
130#else
131 // make relative paths absolute
132 fullFileName = Pgm().GetExecutablePath() + possibilities[i] + shortname;
133#endif
134
135 if( wxFileExists( fullFileName ) )
136 return fullFileName;
137 }
138
139 return shortname;
140
141#endif
142}
143
144
145int ExecuteFile( const wxString& aEditorName, const wxString& aFileName, wxProcess* aCallback,
146 bool aFileForKicad )
147{
148 wxString fullEditorName;
149 std::vector<wxString> params;
150
151#ifdef __UNIX__
152 wxString param;
153 bool inSingleQuotes = false;
154 bool inDoubleQuotes = false;
155
156 auto pushParam =
157 [&]()
158 {
159 if( !param.IsEmpty() )
160 {
161 params.push_back( param );
162 param.clear();
163 }
164 };
165
166 for( wxUniChar ch : aEditorName )
167 {
168 if( inSingleQuotes )
169 {
170 if( ch == '\'' )
171 {
172 pushParam();
173 inSingleQuotes = false;
174 continue;
175 }
176 else
177 {
178 param += ch;
179 }
180 }
181 else if( inDoubleQuotes )
182 {
183 if( ch == '"' )
184 {
185 pushParam();
186 inDoubleQuotes = false;
187 }
188 else
189 {
190 param += ch;
191 }
192 }
193 else if( ch == '\'' )
194 {
195 pushParam();
196 inSingleQuotes = true;
197 }
198 else if( ch == '"' )
199 {
200 pushParam();
201 inDoubleQuotes = true;
202 }
203 else if( ch == ' ' )
204 {
205 pushParam();
206 }
207 else
208 {
209 param += ch;
210 }
211 }
212
213 pushParam();
214
215 if( aFileForKicad )
216 fullEditorName = FindKicadFile( params[0] );
217 else
218 fullEditorName = params[0];
219
220 params.erase( params.begin() );
221#else
222
223 if( aFileForKicad )
224 fullEditorName = FindKicadFile( aEditorName );
225 else
226 fullEditorName = aEditorName;
227#endif
228
229 if( wxFileExists( fullEditorName ) )
230 {
231 std::vector<const wchar_t*> args;
232
233 args.emplace_back( fullEditorName.wc_str() );
234
235 if( !params.empty() )
236 {
237 for( const wxString& p : params )
238 args.emplace_back( p.wc_str() );
239 }
240
241 if( !aFileName.IsEmpty() )
242 args.emplace_back( aFileName.wc_str() );
243
244 args.emplace_back( nullptr );
245
246 return wxExecute( const_cast<wchar_t**>( args.data() ), wxEXEC_ASYNC, aCallback );
247 }
248
249 wxString msg;
250 msg.Printf( _( "Command '%s' could not be found." ), fullEditorName );
251 DisplayErrorMessage( nullptr, msg );
252 return -1;
253}
254
255
256bool OpenPDF( const wxString& file )
257{
258 wxString msg;
259 wxString filename = file;
260
262
263 if( Pgm().UseSystemPdfBrowser() )
264 {
265 if( !LaunchExternal( filename ) )
266 {
267 msg.Printf( _( "Unable to find a PDF viewer for '%s'." ), filename );
268 DisplayErrorMessage( nullptr, msg );
269 return false;
270 }
271 }
272 else
273 {
274 const wchar_t* args[3];
275
276 args[0] = Pgm().GetPdfBrowserName().wc_str();
277 args[1] = filename.wc_str();
278 args[2] = nullptr;
279
280 if( wxExecute( const_cast<wchar_t**>( args ) ) == -1 )
281 {
282 msg.Printf( _( "Problem while running the PDF viewer '%s'." ), args[0] );
283 DisplayErrorMessage( nullptr, msg );
284 return false;
285 }
286 }
287
288 return true;
289}
290
291
292void KiCopyFile( const wxString& aSrcPath, const wxString& aDestPath, wxString& aErrors )
293{
294 if( !wxCopyFile( aSrcPath, aDestPath ) )
295 {
296 wxString msg;
297
298 if( !aErrors.IsEmpty() )
299 aErrors += "\n";
300
301 msg.Printf( _( "Cannot copy file '%s'." ), aDestPath );
302 aErrors += msg;
303 }
304}
305
306
307static void traverseSEXPR( SEXPR::SEXPR* aNode, const std::function<void( SEXPR::SEXPR* )>& aVisitor )
308{
309 aVisitor( aNode );
310
311 if( aNode->IsList() )
312 {
313 for( unsigned i = 0; i < aNode->GetNumberOfChildren(); i++ )
314 traverseSEXPR( aNode->GetChild( i ), aVisitor );
315 }
316}
317
318
319void CopySexprFile( const wxString& aSrcPath, const wxString& aDestPath,
320 std::function<bool( const std::string& token, wxString& value )> aCallback,
321 wxString& aErrors )
322{
323 bool success = false;
324
325 try
326 {
327 SEXPR::PARSER parser;
328 std::unique_ptr<SEXPR::SEXPR> sexpr( parser.ParseFromFile( TO_UTF8( aSrcPath ) ) );
329
330 traverseSEXPR( sexpr.get(),
331 [&]( SEXPR::SEXPR* node )
332 {
333 if( node->IsList() && node->GetNumberOfChildren() > 1 && node->GetChild( 0 )->IsSymbol() )
334 {
335 std::string token = node->GetChild( 0 )->GetSymbol();
336 SEXPR::SEXPR_STRING* pathNode = dynamic_cast<SEXPR::SEXPR_STRING*>( node->GetChild( 1 ) );
337 SEXPR::SEXPR_SYMBOL* symNode = dynamic_cast<SEXPR::SEXPR_SYMBOL*>( node->GetChild( 1 ) );
338 wxString path;
339
340 if( pathNode )
341 path = pathNode->m_value;
342 else if( symNode )
343 path = symNode->m_value;
344
345 if( aCallback( token, path ) )
346 {
347 if( pathNode )
348 pathNode->m_value = path;
349 else if( symNode )
350 symNode->m_value = path;
351 }
352 }
353 } );
354
355 wxFFile destFile( aDestPath, "wb" );
356
357 if( destFile.IsOpened() )
358 success = destFile.Write( sexpr->AsString( 0 ) );
359
360 // wxFFile dtor will close the file
361 }
362 catch( ... )
363 {
364 success = false;
365 }
366
367 if( !success )
368 {
369 wxString msg;
370
371 if( !aErrors.empty() )
372 aErrors += wxS( "\n" );
373
374 msg.Printf( _( "Cannot copy file '%s'." ), aDestPath );
375 aErrors += msg;
376 }
377}
378
379
380wxString QuoteFullPath( wxFileName& fn, wxPathFormat format )
381{
382 return wxT( "\"" ) + fn.GetFullPath( format ) + wxT( "\"" );
383}
384
385
386bool RmDirRecursive( const wxString& aFileName, wxString* aErrors )
387{
388 namespace fs = std::filesystem;
389
390 std::string rmDir = aFileName.ToStdString();
391
392 if( rmDir.length() < 3 )
393 {
394 if( aErrors )
395 *aErrors = _( "Invalid directory name, cannot remove root" );
396
397 return false;
398 }
399
400 if( !fs::exists( rmDir ) )
401 {
402 if( aErrors )
403 *aErrors = wxString::Format( _( "Directory '%s' does not exist" ), aFileName );
404
405 return false;
406 }
407
408 fs::path path( rmDir );
409
410 if( !fs::is_directory( path ) )
411 {
412 if( aErrors )
413 *aErrors = wxString::Format( _( "'%s' is not a directory" ), aFileName );
414
415 return false;
416 }
417
418 try
419 {
420 fs::remove_all( path );
421 }
422 catch( const fs::filesystem_error& e )
423 {
424 if( aErrors )
425 *aErrors = wxString::Format( _( "Error removing directory '%s': %s" ),
426 aFileName, e.what() );
427
428 return false;
429 }
430
431 return true;
432}
433
434
435bool CopyDirectory( const wxString& aSourceDir, const wxString& aDestDir, wxString& aErrors )
436{
437 wxDir dir( aSourceDir );
438
439 if( !dir.IsOpened() )
440 {
441 aErrors += wxString::Format( _( "Could not open source directory: %s" ), aSourceDir );
442 aErrors += wxT( "\n" );
443 return false;
444 }
445
446 if( !wxFileName::Mkdir( aDestDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
447 {
448 aErrors += wxString::Format( _( "Could not create destination directory: %s" ), aDestDir );
449 aErrors += wxT( "\n" );
450 return false;
451 }
452
453 wxString filename;
454 bool cont = dir.GetFirst( &filename );
455
456 while( cont )
457 {
458 wxString sourcePath = aSourceDir + wxFileName::GetPathSeparator() + filename;
459 wxString destPath = aDestDir + wxFileName::GetPathSeparator() + filename;
460
461 if( wxFileName::DirExists( sourcePath ) )
462 {
463 // Recursively copy subdirectories
464 if( !CopyDirectory( sourcePath, destPath, aErrors ) )
465 return false;
466 }
467 else
468 {
469 // Copy files
470 if( !wxCopyFile( sourcePath, destPath ) )
471 {
472 aErrors += wxString::Format( _( "Could not copy file: %s to %s" ),
473 sourcePath,
474 destPath );
475 return false;
476 }
477 }
478
479 cont = dir.GetNext( &filename );
480 }
481
482 return true;
483}
484
485
486bool CopyFilesOrDirectory( const wxString& aSourcePath, const wxString& aDestDir, wxString& aErrors,
487 int& aFileCopiedCount, const std::vector<wxString>& aExclusions )
488{
489 // Parse source path and determine if it's a directory
490 wxFileName sourceFn( aSourcePath );
491 wxString sourcePath = sourceFn.GetFullPath();
492 bool isSourceDirectory = wxFileName::DirExists( sourcePath );
493 wxString baseDestDir = aDestDir;
494
495 auto performCopy = [&]( const wxString& src, const wxString& dest ) -> bool
496 {
497 if( wxCopyFile( src, dest ) )
498 {
499 aFileCopiedCount++;
500 return true;
501 }
502
503 aErrors += wxString::Format( _( "Could not copy file: %s to %s" ), src, dest );
504 aErrors += wxT( "\n" );
505 return false;
506 };
507
508 auto processEntries = [&]( const wxString& srcDir, const wxString& pattern,
509 const wxString& destDir ) -> bool
510 {
511 wxDir dir( srcDir );
512
513 if( !dir.IsOpened() )
514 {
515 aErrors += wxString::Format( _( "Could not open source directory: %s" ), srcDir );
516 aErrors += wxT( "\n" );
517 return false;
518 }
519
520 wxString filename;
521 bool success = true;
522
523 // Find all entries matching pattern (files + directories + hidden items)
524 bool cont = dir.GetFirst( &filename, pattern, wxDIR_FILES | wxDIR_DIRS | wxDIR_HIDDEN );
525
526 while( cont )
527 {
528 const wxString entrySrc = srcDir + wxFileName::GetPathSeparator() + filename;
529 const wxString entryDest = destDir + wxFileName::GetPathSeparator() + filename;
530
531 // Apply exclusion filters
532 bool exclude =
533 filename.Matches( wxT( "~*.lck" ) ) || filename.Matches( wxT( "*.lck" ) );
534
535 for( const auto& exclusion : aExclusions )
536 {
537 if( entrySrc.Matches( exclusion ) )
538 {
539 exclude = true;
540 break;
541 }
542 }
543
544 if( !exclude )
545 {
546 if( wxFileName::DirExists( entrySrc ) )
547 {
548 // Recursively process subdirectories
549 if( !CopyFilesOrDirectory( entrySrc, destDir, aErrors, aFileCopiedCount,
550 aExclusions ) )
551 {
552 aErrors += wxString::Format( _( "Could not copy directory: %s to %s" ),
553 entrySrc, entryDest );
554 aErrors += wxT( "\n" );
555
556 success = false;
557 }
558 }
559 else
560 {
561 // Copy individual files
562 if( !performCopy( entrySrc, entryDest ) )
563 {
564 success = false;
565 }
566 }
567 }
568
569 cont = dir.GetNext( &filename );
570 }
571
572 return success;
573 };
574
575 // If copying a directory, append its name to destination path
576 if( isSourceDirectory )
577 {
578 wxString sourceDirName = sourceFn.GetFullName();
579 baseDestDir = wxFileName( aDestDir, sourceDirName ).GetFullPath();
580 }
581
582 // Create destination directory hierarchy
583 if( !wxFileName::Mkdir( baseDestDir, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
584 {
585 aErrors +=
586 wxString::Format( _( "Could not create destination directory: %s" ), baseDestDir );
587 aErrors += wxT( "\n" );
588
589 return false;
590 }
591
592 // Execute appropriate copy operation based on source type
593 if( !isSourceDirectory )
594 {
595 const wxString fileName = sourceFn.GetFullName();
596
597 // Handle wildcard patterns in filenames
598 if( fileName.Contains( '*' ) || fileName.Contains( '?' ) )
599 {
600 const wxString dirPath = sourceFn.GetPath();
601
602 if( !wxFileName::DirExists( dirPath ) )
603 {
604 aErrors += wxString::Format( _( "Source directory does not exist: %s" ), dirPath );
605 aErrors += wxT( "\n" );
606
607 return false;
608 }
609 // Process all matching files in source directory
610 return processEntries( dirPath, fileName, baseDestDir );
611 }
612
613 // Single file copy operation
614 return performCopy( sourcePath, wxFileName( baseDestDir, fileName ).GetFullPath() );
615 }
616
617 // Full directory copy operation
618 return processEntries( sourcePath, wxEmptyString, baseDestDir );
619}
620
621
622bool AddDirectoryToZip( wxZipOutputStream& aZip, const wxString& aSourceDir, wxString& aErrors,
623 const wxString& aParentDir )
624{
625 wxDir dir( aSourceDir );
626
627 if( !dir.IsOpened() )
628 {
629 aErrors += wxString::Format( _( "Could not open source directory: %s" ), aSourceDir );
630 aErrors += "\n";
631 return false;
632 }
633
634 wxString filename;
635 bool cont = dir.GetFirst( &filename );
636
637 while( cont )
638 {
639 wxString sourcePath = aSourceDir + wxFileName::GetPathSeparator() + filename;
640 wxString zipPath = aParentDir + filename;
641
642 if( wxFileName::DirExists( sourcePath ) )
643 {
644 // Add directory entry to the ZIP file
645 aZip.PutNextDirEntry( zipPath + "/" );
646
647 // Recursively add subdirectories
648 if( !AddDirectoryToZip( aZip, sourcePath, aErrors, zipPath + "/" ) )
649 return false;
650 }
651 else
652 {
653 // Add file entry to the ZIP file
654 aZip.PutNextEntry( zipPath );
655 wxFFileInputStream fileStream( sourcePath );
656
657 if( !fileStream.IsOk() )
658 {
659 aErrors += wxString::Format( _( "Could not read file: %s" ), sourcePath );
660 return false;
661 }
662
663 aZip.Write( fileStream );
664 }
665
666 cont = dir.GetNext( &filename );
667 }
668
669 return true;
670}
constexpr std::size_t arrayDim(T const (&)[N]) noexcept
Returns # of elements in an array.
Definition arraydim.h:31
virtual const wxString & GetKicadEnvVariable() const
Definition pgm_base.h:176
virtual void ReadPdfBrowserInfos()
Read the PDF browser choice from the common configuration.
Definition pgm_base.cpp:857
virtual const wxString & GetPdfBrowserName() const
Definition pgm_base.h:182
virtual const wxString & GetExecutablePath() const
Definition pgm_base.cpp:851
std::unique_ptr< SEXPR > ParseFromFile(const std::string &aFilename)
size_t GetNumberOfChildren() const
Definition sexpr.cpp:71
bool IsList() const
Definition sexpr.h:49
SEXPR * GetChild(size_t aIndex) const
Definition sexpr.cpp:49
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:202
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:60
wxString QuoteFullPath(wxFileName &fn, wxPathFormat format)
Quote return value of wxFileName::GetFullPath().
Definition gestfich.cpp:380
void CopySexprFile(const wxString &aSrcPath, const wxString &aDestPath, std::function< bool(const std::string &token, wxString &value)> aCallback, wxString &aErrors)
Definition gestfich.cpp:319
bool OpenPDF(const wxString &file)
Run the PDF viewer and display a PDF file.
Definition gestfich.cpp:256
void KiCopyFile(const wxString &aSrcPath, const wxString &aDestPath, wxString &aErrors)
Definition gestfich.cpp:292
int ExecuteFile(const wxString &aEditorName, const wxString &aFileName, wxProcess *aCallback, bool aFileForKicad)
Call the executable file aEditorName with the parameter aFileName.
Definition gestfich.cpp:145
bool CopyFilesOrDirectory(const wxString &aSourcePath, const wxString &aDestDir, wxString &aErrors, int &aFileCopiedCount, const std::vector< wxString > &aExclusions)
Definition gestfich.cpp:486
bool RmDirRecursive(const wxString &aFileName, wxString *aErrors)
Remove the directory aDirName and all its contents including subdirectories and their files.
Definition gestfich.cpp:386
void QuoteString(wxString &string)
Add un " to the start and the end of string (if not already done).
Definition gestfich.cpp:50
static void traverseSEXPR(SEXPR::SEXPR *aNode, const std::function< void(SEXPR::SEXPR *)> &aVisitor)
Definition gestfich.cpp:307
bool CopyDirectory(const wxString &aSourceDir, const wxString &aDestDir, wxString &aErrors)
Copy a directory and its contents to another directory.
Definition gestfich.cpp:435
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:622
bool LaunchExternal(const wxString &aPath)
Launches the given file or folder in the host OS.
PGM_BASE & Pgm()
The global program "get" accessor.
Definition pgm_base.cpp:946
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