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