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