KiCad PCB EDA Suite
Loading...
Searching...
No Matches
project_archiver.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include <memory>
21#include <wx/dir.h>
22#include <wx/filedlg.h>
23#include <wx/fs_zip.h>
24#include <wx/uri.h>
25#include <wx/wfstream.h>
26#include <wx/zipstrm.h>
27
28#include <core/arraydim.h>
29#include <macros.h>
31#include <reporter.h>
33#include <wxstream_helper.h>
34#include <wx/log.h>
35#include <kiplatform/io.h>
36
37#include <regex>
38#include <set>
39
40
41#define ZipFileExtension wxT( "zip" )
42
43class PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER : public wxDirTraverser
44{
45public:
46 PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER( const std::string& aExtRegex, const wxString& aPrjDir,
47 wxZipOutputStream& aZipFileOutput,
48 REPORTER& aReporter, bool aVerbose ) :
49 m_zipFile( aZipFileOutput ),
50 m_prjDir( aPrjDir ),
51 m_fileExtRegex( aExtRegex, std::regex_constants::ECMAScript | std::regex_constants::icase ),
52 m_reporter( aReporter ),
53 m_errorOccurred( false ),
54 m_verbose( aVerbose )
55 {}
56
57 virtual wxDirTraverseResult OnFile( const wxString& aFilename ) override
58 {
59 if( std::regex_search( aFilename.ToStdString(), m_fileExtRegex ) )
60 {
61 addFileToZip( aFilename );
62
63 // Special processing for IBIS files to include the corresponding pkg file
64 if( aFilename.EndsWith( FILEEXT::IbisFileExtension ) )
65 {
66 wxFileName package( aFilename );
67 package.MakeRelativeTo( m_prjDir );
68 package.SetExt( wxS( "pkg" ) );
70
71 if( package.Exists() )
72 addFileToZip( package.GetFullPath() );
73 }
74 }
75
76 return wxDIR_CONTINUE;
77 }
78
79 virtual wxDirTraverseResult OnDir( const wxString& aDirname ) override
80 {
81 return wxDIR_CONTINUE;
82 }
83
84 unsigned long GetUncompressedBytes() const
85 {
87 }
88
89 bool GetErrorOccurred() const
90 {
91 return m_errorOccurred;
92 }
93
94private:
95 void addFileToZip( const wxString& aFilename )
96 {
97 wxString msg;
98 wxFileSystem fsfile;
99
100 wxFileName curr_fn( aFilename );
101 wxFileName curr_prjdir;
102 curr_prjdir.AssignDir( m_prjDir );
103
106
107 // Note: MakeRelativeTo() works only if curr_fn and curr_prjdir use the same
108 // long path adjustment (no long path of both use long path)
109 curr_fn.MakeRelativeTo( curr_prjdir.GetFullPath() );
110
111 wxString currFilename = curr_fn.GetFullPath();
112
113 // Read input file and add it to the zip file:
114 wxFSFile* infile = fsfile.OpenFile( currFilename );
115
116 if( infile )
117 {
118 m_zipFile.PutNextEntry( currFilename, infile->GetModificationTime() );
119 infile->GetStream()->Read( m_zipFile );
120 m_zipFile.CloseEntry();
121
122 m_uncompressedBytes += infile->GetStream()->GetSize();
123
124 if( m_verbose )
125 {
126 msg.Printf( _( "Archived file '%s'." ), currFilename );
128 }
129
130 delete infile;
131 }
132 else
133 {
134 if( m_verbose )
135 {
136 msg.Printf( _( "Failed to archive file '%s'." ), currFilename );
138 }
139
140 m_errorOccurred = true;
141 }
142 }
143
144private:
145 wxZipOutputStream& m_zipFile;
146
147 wxString m_prjDir;
148 std::regex m_fileExtRegex;
150
151 bool m_errorOccurred; // True if an error archiving the file
152 bool m_verbose; // True to enable verbose logging
153
154 // Keep track of how many bytes would have been used without compression
155 unsigned long m_uncompressedBytes = 0;
156};
157
158
160{
161}
162
163
164bool PROJECT_ARCHIVER::AreZipArchivesIdentical( const wxString& aZipFileA,
165 const wxString& aZipFileB, REPORTER& aReporter )
166{
167 wxFFileInputStream streamA( aZipFileA );
168 wxFFileInputStream streamB( aZipFileB );
169
170 if( !streamA.IsOk() || !streamB.IsOk() )
171 {
172 aReporter.Report( _( "Could not open archive file." ), RPT_SEVERITY_ERROR );
173 return false;
174 }
175
176 wxZipInputStream zipStreamA = wxZipInputStream( streamA );
177 wxZipInputStream zipStreamB = wxZipInputStream( streamB );
178
179 std::set<wxUint32> crcsA;
180 std::set<wxUint32> crcsB;
181
182
183 for( wxZipEntry* entry = zipStreamA.GetNextEntry(); entry; entry = zipStreamA.GetNextEntry() )
184 {
185 crcsA.insert( entry->GetCrc() );
186 }
187
188 for( wxZipEntry* entry = zipStreamB.GetNextEntry(); entry; entry = zipStreamB.GetNextEntry() )
189 {
190 crcsB.insert( entry->GetCrc() );
191 }
192
193 return crcsA == crcsB;
194}
195
196
197// Unarchive Files code comes from wxWidgets sample/archive/archive.cpp
198bool PROJECT_ARCHIVER::Unarchive( const wxString& aSrcFile, const wxString& aDestDir,
199 REPORTER& aReporter )
200{
201 wxFFileInputStream stream( aSrcFile );
202
203 if( !stream.IsOk() )
204 {
205 aReporter.Report( _( "Could not open archive file." ), RPT_SEVERITY_ERROR );
206 return false;
207 }
208
209 const wxArchiveClassFactory* archiveClassFactory =
210 wxArchiveClassFactory::Find( aSrcFile, wxSTREAM_FILEEXT );
211
212 if( !archiveClassFactory )
213 {
214 aReporter.Report( _( "Invalid archive file format." ), RPT_SEVERITY_ERROR );
215 return false;
216 }
217
218 std::unique_ptr<wxArchiveInputStream> archiveStream( archiveClassFactory->NewStream( stream ) );
219
220 wxString fileStatus;
221
222 for( wxArchiveEntry* entry = archiveStream->GetNextEntry(); entry;
223 entry = archiveStream->GetNextEntry() )
224 {
225 fileStatus.Printf( _( "Extracting file '%s'." ), entry->GetName() );
226 aReporter.Report( fileStatus, RPT_SEVERITY_INFO );
227
228 wxString fullname = aDestDir + entry->GetName();
229
230 // Ensure the target directory exists and create it if not
231 wxString t_path = wxPathOnly( fullname );
232
233 if( !wxDirExists( t_path ) )
234 {
235 wxFileName::Mkdir( t_path, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
236 }
237
238 // Directory entries need only be created, not extracted (0 size)
239 if( entry->IsDir() )
240 continue;
241
242
243 wxTempFileOutputStream outputFileStream( fullname );
244
245 if( CopyStreamData( *archiveStream, outputFileStream, entry->GetSize() ) )
246 outputFileStream.Commit();
247 else
248 aReporter.Report( _( "Error extracting file!" ), RPT_SEVERITY_ERROR );
249
250 // Now let's set the filetimes based on what's in the zip
251 wxFileName outputFileName( fullname );
252 wxDateTime fileTime = entry->GetDateTime();
253
254 // For now we set access, mod, create to the same datetime
255 // create (third arg) is only used on Windows
256 outputFileName.SetTimes( &fileTime, &fileTime, &fileTime );
257 }
258
259 aReporter.Report( wxT( "Extracted project." ), RPT_SEVERITY_INFO );
260 return true;
261}
262
263
264bool PROJECT_ARCHIVER::Archive( const wxString& aSrcDir, const wxString& aDestFile,
265 REPORTER& aReporter, bool aVerbose, bool aIncludeExtraFiles )
266{
267
268#define EXT( ext ) "\\." + ext + "|"
269#define NAME( name ) name + "|"
270#define EXT_NO_PIPE( ext ) "\\." + ext
271#define NAME_NO_PIPE( name ) name
272
273 // List of file extensions that are always archived
274 std::string fileExtensionRegex = "("
284 EXT( FILEEXT::JsonFileExtension ) // for design blocks
289
290 // List of additional file extensions that are only archived when aIncludeExtraFiles is true
291 if( aIncludeExtraFiles )
292 {
293 fileExtensionRegex += "|"
302 EXT( FILEEXT::StepFileExtension ) // 3d files
303 EXT( FILEEXT::VrmlFileExtension ) // 3d files
304 EXT( FILEEXT::GerberFileExtensionsRegex ) // Gerber files (g?, g??, .gm12 (from protel export))
305 EXT( FILEEXT::GerberJobFileExtension ) // Gerber job files
306 EXT( FILEEXT::FootprintPlaceFileExtension ) // Our position files
307 EXT( FILEEXT::DrillFileExtension ) // Fab drill files
308 EXT( "nc" ) // Fab drill files
309 EXT( "xnc" ) // Fab drill files
316 EXT( FILEEXT::SpiceFileExtension ) // SPICE files
318 EXT( FILEEXT::SpiceModelFileExtension ) // SPICE files
320 }
321
322 fileExtensionRegex += ")";
323
324#undef EXT
325#undef NAME
326#undef EXT_NO_PIPE
327#undef NAME_NO_PIPE
328
329 bool success = true;
330 wxString msg;
331 wxString oldCwd = wxGetCwd();
332
333 wxFileName sourceDir( aSrcDir );
335 wxSetWorkingDirectory( sourceDir.GetFullPath() );
336
337 wxFFileOutputStream ostream( aDestFile );
338
339 if( !ostream.IsOk() ) // issue to create the file. Perhaps not writable dir
340 {
341 msg.Printf( _( "Failed to create file '%s'." ), aDestFile );
342 aReporter.Report( msg, RPT_SEVERITY_ERROR );
343 return false;
344 }
345
346 wxZipOutputStream zipstream( ostream, -1, wxConvUTF8 );
347
348 wxDir projectDir( aSrcDir );
349 wxString currFilename;
350 wxArrayString files;
351
352 if( !projectDir.IsOpened() )
353 {
354 if( aVerbose )
355 {
356 msg.Printf( _( "Error opening directory: '%s'." ), aSrcDir );
357 aReporter.Report( msg, RPT_SEVERITY_ERROR );
358 }
359
360 wxSetWorkingDirectory( oldCwd );
361 return false;
362 }
363
364 try
365 {
366 PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER traverser( fileExtensionRegex, aSrcDir, zipstream,
367 aReporter, aVerbose );
368
369 projectDir.Traverse( traverser );
370
371 success = !traverser.GetErrorOccurred();
372
373 auto reportSize =
374 []( unsigned long aSize ) -> wxString
375 {
376 constexpr float KB = 1024.0;
377 constexpr float MB = KB * 1024.0;
378
379 if( aSize >= MB )
380 return wxString::Format( wxT( "%0.2f MB" ), aSize / MB );
381 else if( aSize >= KB )
382 return wxString::Format( wxT( "%0.2f KB" ), aSize / KB );
383 else
384 return wxString::Format( wxT( "%lu bytes" ), aSize );
385 };
386
387 size_t zipBytesCnt = ostream.GetSize();
388 unsigned long uncompressedBytes = traverser.GetUncompressedBytes();
389
390 if( zipstream.Close() )
391 {
392 msg.Printf( _( "Zip archive '%s' created (%s uncompressed, %s compressed)." ),
393 aDestFile,
394 reportSize( uncompressedBytes ),
395 reportSize( zipBytesCnt ) );
396 aReporter.Report( msg, RPT_SEVERITY_INFO );
397 }
398 else
399 {
400 msg.Printf( wxT( "Failed to create file '%s'." ), aDestFile );
401 aReporter.Report( msg, RPT_SEVERITY_ERROR );
402 success = false;
403 }
404 }
405 catch( const std::regex_error& e )
406 {
407 // Something bad happened here with the regex
408 wxASSERT_MSG( false, e.what() );
409
410 if( aVerbose )
411 {
412 msg.Printf( _( "Error: '%s'." ), e.what() );
413 aReporter.Report( msg, RPT_SEVERITY_ERROR );
414 }
415
416 success = false;
417 }
418
419 wxSetWorkingDirectory( oldCwd );
420 return success;
421}
virtual wxDirTraverseResult OnDir(const wxString &aDirname) override
PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER(const std::string &aExtRegex, const wxString &aPrjDir, wxZipOutputStream &aZipFileOutput, REPORTER &aReporter, bool aVerbose)
virtual wxDirTraverseResult OnFile(const wxString &aFilename) override
void addFileToZip(const wxString &aFilename)
unsigned long GetUncompressedBytes() const
static bool Archive(const wxString &aSrcDir, const wxString &aDestFile, REPORTER &aReporter, bool aVerbose=true, bool aIncludeExtraFiles=false)
Create an archive of the project.
static bool Unarchive(const wxString &aSrcFile, const wxString &aDestDir, REPORTER &aReporter)
Extract an archive of the current project over existing files.
static bool AreZipArchivesIdentical(const wxString &aZipFileA, const wxString &aZipFileB, REPORTER &aReporter)
Compare the CRCs of all the files in zip archive to determine whether the archives are identical.
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:72
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)=0
Report a string with a given severity.
#define _(s)
static const std::string LegacySchematicFileExtension
static const wxString GerberFileExtensionsRegex
static const std::string NetlistFileExtension
static const std::string SymbolLibraryTableFileName
static const std::string GerberJobFileExtension
static const std::string StepFileAbrvExtension
static const std::string WorkbookFileExtension
static const std::string ReportFileExtension
static const std::string ProjectFileExtension
static const std::string FootprintPlaceFileExtension
static const std::string JsonFileExtension
static const std::string LegacyPcbFileExtension
static const std::string LegacyProjectFileExtension
static const std::string ProjectLocalSettingsFileExtension
static const std::string KiCadSchematicFileExtension
static const std::string LegacySymbolLibFileExtension
static const std::string DesignBlockLibraryTableFileName
static const std::string KiCadSymbolLibFileExtension
static const std::string SpiceFileExtension
static const std::string PdfFileExtension
static const std::string TextFileExtension
static const std::string FootprintLibraryTableFileName
static const std::string DrawingSheetFileExtension
static const std::string IbisFileExtension
static const std::string IpcD356FileExtension
static const std::string KiCadJobSetFileExtension
static const std::string LegacyFootprintLibPathExtension
static const std::string PythonFileExtension
static const std::string StepFileExtension
static const std::string LegacySymbolDocumentFileExtension
static const std::string FootprintAssignmentFileExtension
static const std::string DrillFileExtension
static const std::string SpiceSubcircuitFileExtension
static const std::string SpiceModelFileExtension
static const std::string DesignRulesFileExtension
static const std::string VrmlFileExtension
static const std::string KiCadFootprintFileExtension
static const std::string KiCadPcbFileExtension
This file contains miscellaneous commonly used macros and functions.
void LongPathAdjustment(wxFileName &aFilename)
Adjusts a filename to be a long path compatible.
Definition: unix/io.cpp:78
STL namespace.
#define EXT(ext)
#define NAME(name)
#define EXT_NO_PIPE(ext)
#define NAME_NO_PIPE(name)
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO
Definition of file extensions used in Kicad.
static bool CopyStreamData(wxInputStream &inputStream, wxOutputStream &outputStream, wxFileOffset size)