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/regex.h>
25#include <wx/uri.h>
26#include <wx/wfstream.h>
27#include <wx/zipstrm.h>
28
29#include <core/arraydim.h>
30#include <macros.h>
32#include <reporter.h>
34#include <wxstream_helper.h>
35#include <wx/log.h>
36#include <kiplatform/io.h>
37
38#include <regex>
39#include <set>
40
41
42#define ZipFileExtension wxT( "zip" )
43
44class PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER : public wxDirTraverser
45{
46public:
47 PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER( const wxString& aPrjDir ) :
48 m_prjDir( aPrjDir )
49 {}
50
51 virtual wxDirTraverseResult OnFile( const wxString& aFilename ) override
52 {
53 m_files.emplace_back( aFilename );
54
55 return wxDIR_CONTINUE;
56 }
57
58 virtual wxDirTraverseResult OnDir( const wxString& aDirname ) override
59 {
60 return wxDIR_CONTINUE;
61 }
62
63 const std::vector<wxString>& GetFilesToArchive() const
64 {
65 return m_files;
66 }
67
68private:
69 wxString m_prjDir;
70 std::vector<wxString> m_files;
71};
72
73
75{
76}
77
78
79bool PROJECT_ARCHIVER::AreZipArchivesIdentical( const wxString& aZipFileA,
80 const wxString& aZipFileB, REPORTER& aReporter )
81{
82 wxFFileInputStream streamA( aZipFileA );
83 wxFFileInputStream streamB( aZipFileB );
84
85 if( !streamA.IsOk() || !streamB.IsOk() )
86 {
87 aReporter.Report( _( "Could not open archive file." ), RPT_SEVERITY_ERROR );
88 return false;
89 }
90
91 wxZipInputStream zipStreamA = wxZipInputStream( streamA );
92 wxZipInputStream zipStreamB = wxZipInputStream( streamB );
93
94 std::set<wxUint32> crcsA;
95 std::set<wxUint32> crcsB;
96
97
98 for( wxZipEntry* entry = zipStreamA.GetNextEntry(); entry; entry = zipStreamA.GetNextEntry() )
99 {
100 crcsA.insert( entry->GetCrc() );
101 }
102
103 for( wxZipEntry* entry = zipStreamB.GetNextEntry(); entry; entry = zipStreamB.GetNextEntry() )
104 {
105 crcsB.insert( entry->GetCrc() );
106 }
107
108 return crcsA == crcsB;
109}
110
111
112// Unarchive Files code comes from wxWidgets sample/archive/archive.cpp
113bool PROJECT_ARCHIVER::Unarchive( const wxString& aSrcFile, const wxString& aDestDir,
114 REPORTER& aReporter )
115{
116 wxFFileInputStream stream( aSrcFile );
117
118 if( !stream.IsOk() )
119 {
120 aReporter.Report( _( "Could not open archive file." ), RPT_SEVERITY_ERROR );
121 return false;
122 }
123
124 const wxArchiveClassFactory* archiveClassFactory =
125 wxArchiveClassFactory::Find( aSrcFile, wxSTREAM_FILEEXT );
126
127 if( !archiveClassFactory )
128 {
129 aReporter.Report( _( "Invalid archive file format." ), RPT_SEVERITY_ERROR );
130 return false;
131 }
132
133 std::unique_ptr<wxArchiveInputStream> archiveStream( archiveClassFactory->NewStream( stream ) );
134
135 wxString fileStatus;
136
137 for( wxArchiveEntry* entry = archiveStream->GetNextEntry(); entry;
138 entry = archiveStream->GetNextEntry() )
139 {
140 fileStatus.Printf( _( "Extracting file '%s'." ), entry->GetName() );
141 aReporter.Report( fileStatus, RPT_SEVERITY_INFO );
142
143 wxString fullname = aDestDir + entry->GetName();
144
145 // Ensure the target directory exists and create it if not
146 wxString t_path = wxPathOnly( fullname );
147
148 if( !wxDirExists( t_path ) )
149 {
150 wxFileName::Mkdir( t_path, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
151 }
152
153 // Directory entries need only be created, not extracted (0 size)
154 if( entry->IsDir() )
155 continue;
156
157
158 wxTempFileOutputStream outputFileStream( fullname );
159
160 if( CopyStreamData( *archiveStream, outputFileStream, entry->GetSize() ) )
161 outputFileStream.Commit();
162 else
163 aReporter.Report( _( "Error extracting file!" ), RPT_SEVERITY_ERROR );
164
165 // Now let's set the filetimes based on what's in the zip
166 wxFileName outputFileName( fullname );
167 wxDateTime fileTime = entry->GetDateTime();
168
169 // For now we set access, mod, create to the same datetime
170 // create (third arg) is only used on Windows
171 outputFileName.SetTimes( &fileTime, &fileTime, &fileTime );
172 }
173
174 aReporter.Report( wxT( "Extracted project." ), RPT_SEVERITY_INFO );
175 return true;
176}
177
178
179bool PROJECT_ARCHIVER::Archive( const wxString& aSrcDir, const wxString& aDestFile,
180 REPORTER& aReporter, bool aVerbose, bool aIncludeExtraFiles )
181{
182
183 std::set<wxString> extensions;
184 std::set<wxString> files; // File names without extensions such as fp-lib-table.
185
186 extensions.emplace( FILEEXT::ProjectFileExtension );
187 extensions.emplace( FILEEXT::ProjectLocalSettingsFileExtension );
188 extensions.emplace( FILEEXT::KiCadSchematicFileExtension );
189 extensions.emplace( FILEEXT::KiCadSymbolLibFileExtension );
190 extensions.emplace( FILEEXT::KiCadPcbFileExtension );
191 extensions.emplace( FILEEXT::KiCadFootprintFileExtension );
192 extensions.emplace( FILEEXT::DesignRulesFileExtension );
193 extensions.emplace( FILEEXT::DrawingSheetFileExtension );
194 extensions.emplace( FILEEXT::KiCadJobSetFileExtension );
195 extensions.emplace( FILEEXT::JsonFileExtension ); // for design blocks
196 extensions.emplace( FILEEXT::WorkbookFileExtension );
197
201
202 // List of additional file extensions that are only archived when aIncludeExtraFiles is true
203 if( aIncludeExtraFiles )
204 {
205 extensions.emplace( FILEEXT::LegacyProjectFileExtension );
206 extensions.emplace( FILEEXT::LegacySchematicFileExtension );
207 extensions.emplace( FILEEXT::LegacySymbolLibFileExtension );
208 extensions.emplace( FILEEXT::LegacySymbolDocumentFileExtension );
209 extensions.emplace( FILEEXT::FootprintAssignmentFileExtension );
210 extensions.emplace( FILEEXT::LegacyPcbFileExtension );
211 extensions.emplace( FILEEXT::LegacyFootprintLibPathExtension );
212 extensions.emplace( FILEEXT::StepFileAbrvExtension );
213 extensions.emplace( FILEEXT::StepFileExtension ); // 3d files
214 extensions.emplace( FILEEXT::VrmlFileExtension ); // 3d files
215 extensions.emplace( FILEEXT::GerberJobFileExtension ); // Gerber job files
216 extensions.emplace( FILEEXT::FootprintPlaceFileExtension ); // Our position files
217 extensions.emplace( FILEEXT::DrillFileExtension ); // Fab drill files
218 extensions.emplace( "nc" ); // Fab drill files
219 extensions.emplace( "xnc" ); // Fab drill files
220 extensions.emplace( FILEEXT::IpcD356FileExtension );
221 extensions.emplace( FILEEXT::ReportFileExtension );
222 extensions.emplace( FILEEXT::NetlistFileExtension );
223 extensions.emplace( FILEEXT::PythonFileExtension );
224 extensions.emplace( FILEEXT::PdfFileExtension );
225 extensions.emplace( FILEEXT::TextFileExtension );
226 extensions.emplace( FILEEXT::SpiceFileExtension ); // SPICE files
227 extensions.emplace( FILEEXT::SpiceSubcircuitFileExtension ); // SPICE files
228 extensions.emplace( FILEEXT::SpiceModelFileExtension ); // SPICE files
229 extensions.emplace( FILEEXT::IbisFileExtension );
230 extensions.emplace( "pkg" );
231 extensions.emplace( FILEEXT::GencadFileExtension );
232 }
233
234 // Gerber files (g?, g??, .gm12 (from protel export)).
235 wxRegEx gerberFiles( FILEEXT::GerberFileExtensionsRegex );
236 wxASSERT( gerberFiles.IsValid() );
237
238 bool success = true;
239 wxString msg;
240 wxString oldCwd = wxGetCwd();
241
242 wxFileName sourceDir( aSrcDir, wxEmptyString, wxEmptyString );
243
244 wxSetWorkingDirectory( aSrcDir );
245
246 wxFFileOutputStream ostream( aDestFile );
247
248 if( !ostream.IsOk() ) // issue to create the file. Perhaps not writable dir
249 {
250 msg.Printf( _( "Failed to create file '%s'." ), aDestFile );
251 aReporter.Report( msg, RPT_SEVERITY_ERROR );
252 return false;
253 }
254
255 wxZipOutputStream zipstream( ostream, -1, wxConvUTF8 );
256
257 wxDir projectDir( aSrcDir );
258 wxString currFilename;
259
260 if( !projectDir.IsOpened() )
261 {
262 if( aVerbose )
263 {
264 msg.Printf( _( "Error opening directory: '%s'." ), aSrcDir );
265 aReporter.Report( msg, RPT_SEVERITY_ERROR );
266 }
267
268 wxSetWorkingDirectory( oldCwd );
269 return false;
270 }
271
272 size_t uncompressedBytes = 0;
273 PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER traverser( aSrcDir );
274
275 projectDir.Traverse( traverser );
276
277 for( const wxString& fileName : traverser.GetFilesToArchive() )
278 {
279 wxFileName fn( fileName );
280 wxString extLower = fn.GetExt().Lower();
281 wxString fileNameLower = fn.GetName().Lower();
282 bool archive = false;
283
284 if( !extLower.IsEmpty() )
285 {
286 if( ( extensions.find( extLower ) != extensions.end() )
287 || ( aIncludeExtraFiles && gerberFiles.Matches( extLower ) ) )
288 archive = true;
289 }
290 else if( !fileNameLower.IsEmpty() && ( files.find( fileNameLower ) != files.end() ) )
291 {
292 archive = true;
293 }
294
295 if( !archive )
296 continue;
297
298 wxFileSystem fsFile;
299 fn.MakeRelativeTo( aSrcDir );
300
301 wxString relativeFn = fn.GetFullPath();
302
303 // Read input file and add it to the zip file:
304 wxFSFile* infile = fsFile.OpenFile( relativeFn );
305
306 if( infile )
307 {
308 zipstream.PutNextEntry( relativeFn, infile->GetModificationTime() );
309 infile->GetStream()->Read( zipstream );
310 zipstream.CloseEntry();
311
312 uncompressedBytes += infile->GetStream()->GetSize();
313
314 if( aVerbose )
315 {
316 msg.Printf( _( "Archived file '%s'." ), relativeFn );
317 aReporter.Report( msg, RPT_SEVERITY_INFO );
318 }
319
320 delete infile;
321 }
322 else
323 {
324 if( aVerbose )
325 {
326 msg.Printf( _( "Failed to archive file '%s'." ), relativeFn );
327 aReporter.Report( msg, RPT_SEVERITY_ERROR );
328 }
329 }
330 }
331
332 auto reportSize =
333 []( size_t aSize ) -> wxString
334 {
335 constexpr float KB = 1024.0;
336 constexpr float MB = KB * 1024.0;
337
338 if( aSize >= MB )
339 return wxString::Format( wxT( "%0.2f MB" ), aSize / MB );
340 else if( aSize >= KB )
341 return wxString::Format( wxT( "%0.2f KB" ), aSize / KB );
342 else
343 return wxString::Format( wxT( "%zu bytes" ), aSize );
344 };
345
346 size_t zipBytesCnt = ostream.GetSize();
347
348 if( zipstream.Close() )
349 {
350 msg.Printf( _( "Zip archive '%s' created (%s uncompressed, %s compressed)." ),
351 aDestFile,
352 reportSize( uncompressedBytes ),
353 reportSize( zipBytesCnt ) );
354 aReporter.Report( msg, RPT_SEVERITY_INFO );
355 }
356 else
357 {
358 msg.Printf( wxT( "Failed to create file '%s'." ), aDestFile );
359 aReporter.Report( msg, RPT_SEVERITY_ERROR );
360 success = false;
361 }
362
363 wxSetWorkingDirectory( oldCwd );
364 return success;
365}
const std::vector< wxString > & GetFilesToArchive() const
virtual wxDirTraverseResult OnDir(const wxString &aDirname) override
virtual wxDirTraverseResult OnFile(const wxString &aFilename) override
PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER(const wxString &aPrjDir)
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:73
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)
Report a string with a given severity.
Definition: reporter.h:102
#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 GencadFileExtension
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.
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO
Definition of file extensions used in Kicad.
static bool CopyStreamData(wxInputStream &inputStream, wxOutputStream &outputStream, wxFileOffset size)