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 (C) 2020 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
36#include <set>
37
38
39#define ZipFileExtension wxT( "zip" )
40
42{
43}
44
45bool PROJECT_ARCHIVER::AreZipArchivesIdentical( const wxString& aZipFileA,
46 const wxString& aZipFileB, REPORTER& aReporter )
47{
48 wxFFileInputStream streamA( aZipFileA );
49 wxFFileInputStream streamB( aZipFileB );
50
51 if( !streamA.IsOk() || !streamB.IsOk() )
52 {
53 aReporter.Report( _( "Could not open archive file." ), RPT_SEVERITY_ERROR );
54 return false;
55 }
56
57 wxZipInputStream zipStreamA = wxZipInputStream( streamA );
58 wxZipInputStream zipStreamB = wxZipInputStream( streamB );
59
60 std::set<wxUint32> crcsA;
61 std::set<wxUint32> crcsB;
62
63
64 for( wxZipEntry* entry = zipStreamA.GetNextEntry(); entry; entry = zipStreamA.GetNextEntry() )
65 {
66 crcsA.insert( entry->GetCrc() );
67 }
68
69 for( wxZipEntry* entry = zipStreamB.GetNextEntry(); entry; entry = zipStreamB.GetNextEntry() )
70 {
71 crcsB.insert( entry->GetCrc() );
72 }
73
74 return crcsA == crcsB;
75}
76
77
78// Unarchive Files code comes from wxWidgets sample/archive/archive.cpp
79bool PROJECT_ARCHIVER::Unarchive( const wxString& aSrcFile, const wxString& aDestDir,
80 REPORTER& aReporter )
81{
82 wxFFileInputStream stream( aSrcFile );
83
84 if( !stream.IsOk() )
85 {
86 aReporter.Report( _( "Could not open archive file." ), RPT_SEVERITY_ERROR );
87 return false;
88 }
89
90 const wxArchiveClassFactory* archiveClassFactory =
91 wxArchiveClassFactory::Find( aSrcFile, wxSTREAM_FILEEXT );
92
93 if( !archiveClassFactory )
94 {
95 aReporter.Report( _( "Invalid archive file format." ), RPT_SEVERITY_ERROR );
96 return false;
97 }
98
99 std::unique_ptr<wxArchiveInputStream> archiveStream( archiveClassFactory->NewStream( stream ) );
100
101 wxString fileStatus;
102
103 for( wxArchiveEntry* entry = archiveStream->GetNextEntry(); entry;
104 entry = archiveStream->GetNextEntry() )
105 {
106 fileStatus.Printf( _( "Extracting file '%s'." ), entry->GetName() );
107 aReporter.Report( fileStatus, RPT_SEVERITY_INFO );
108
109 wxString fullname = aDestDir + entry->GetName();
110
111 // Ensure the target directory exists and create it if not
112 wxString t_path = wxPathOnly( fullname );
113
114 if( !wxDirExists( t_path ) )
115 {
116 wxFileName::Mkdir( t_path, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
117 }
118
119 // Directory entries need only be created, not extracted (0 size)
120 if( entry->IsDir() )
121 continue;
122
123
124 wxTempFileOutputStream outputFileStream( fullname );
125
126 if( CopyStreamData( *archiveStream, outputFileStream, entry->GetSize() ) )
127 outputFileStream.Commit();
128 else
129 aReporter.Report( _( "Error extracting file!" ), RPT_SEVERITY_ERROR );
130
131 // Now let's set the filetimes based on what's in the zip
132 wxFileName outputFileName( fullname );
133 wxDateTime fileTime = entry->GetDateTime();
134 // For now we set access, mod, create to the same datetime
135 // create (third arg) is only used on Windows
136 outputFileName.SetTimes( &fileTime, &fileTime, &fileTime );
137 }
138
139 aReporter.Report( wxT( "Extracted project." ), RPT_SEVERITY_INFO );
140 return true;
141}
142
143
144bool PROJECT_ARCHIVER::Archive( const wxString& aSrcDir, const wxString& aDestFile,
145 REPORTER& aReporter, bool aVerbose, bool aIncludeExtraFiles )
146{
147 // List of file extensions that are always archived
148 static const wxChar* extensionList[] = {
149 wxT( "*.kicad_pro" ),
150 wxT( "*.kicad_prl" ),
151 wxT( "*.kicad_sch" ),
152 wxT( "*.kicad_sym" ),
153 wxT( "*.kicad_pcb" ),
154 wxT( "*.kicad_mod" ),
155 wxT( "*.kicad_dru" ),
156 wxT( "*.kicad_wks" ),
157 wxT( "*.kicad_jobset" ),
158 wxT( "*.wbk" ),
159 wxT( "fp-lib-table" ),
160 wxT( "sym-lib-table" )
161 };
162
163 // List of additional file extensions that are only archived when aIncludeExtraFiles is true
164 static const wxChar* extraExtensionList[] = {
165 wxT( "*.pro" ), // Legacy project files
166 wxT( "*.sch" ), // Legacy schematic files
167 wxT( "*.lib" ), wxT( "*.dcm" ), // Legacy schematic library files
168 wxT( "*.cmp" ),
169 wxT( "*.brd" ), // Legacy PCB files
170 wxT( "*.mod" ), // Legacy footprint library files
171 wxT( "*.stp" ), wxT( "*.step" ), // 3d files
172 wxT( "*.wrl" ),
173 wxT( "*.g?" ), wxT( "*.g??" ), // Gerber files
174 wxT( "*.gm??"), // Some gerbers like .gm12 (from protel export)
175 wxT( "*.gbrjob" ), // Gerber job files
176 wxT( "*.pos" ), // our position files
177 wxT( "*.drl" ), wxT( "*.nc" ), wxT( "*.xnc" ), // Fab drill files
178 wxT( "*.d356" ),
179 wxT( "*.rpt" ),
180 wxT( "*.net" ),
181 wxT( "*.py" ),
182 wxT( "*.pdf" ),
183 wxT( "*.txt" ),
184 wxT( "*.cir" ), wxT( "*.sub" ), wxT( "*.model" ), // SPICE files
185 wxT( "*.ibs" )
186 };
187
188 bool success = true;
189 wxString msg;
190 wxString oldCwd = wxGetCwd();
191
192 wxSetWorkingDirectory( aSrcDir );
193
194 wxFFileOutputStream ostream( aDestFile );
195
196 if( !ostream.IsOk() ) // issue to create the file. Perhaps not writable dir
197 {
198 msg.Printf( _( "Failed to create file '%s'." ), aDestFile );
199 aReporter.Report( msg, RPT_SEVERITY_ERROR );
200 return false;
201 }
202
203 wxZipOutputStream zipstream( ostream, -1, wxConvUTF8 );
204
205 // Build list of filenames to put in zip archive
206 wxString currFilename;
207
208 wxArrayString files;
209
210 for( unsigned ii = 0; ii < arrayDim( extensionList ); ii++ )
211 wxDir::GetAllFiles( aSrcDir, &files, extensionList[ii] );
212
213 if( aIncludeExtraFiles )
214 {
215 for( unsigned ii = 0; ii < arrayDim( extraExtensionList ); ii++ )
216 wxDir::GetAllFiles( aSrcDir, &files, extraExtensionList[ii] );
217 }
218
219 for( unsigned ii = 0; ii < files.GetCount(); ++ii )
220 {
221 if( files[ii].EndsWith( wxS( ".ibs" ) ) )
222 {
223 wxFileName package( files[ ii ] );
224 package.MakeRelativeTo( aSrcDir );
225 package.SetExt( wxS( "pkg" ) );
226
227 if( package.Exists() )
228 files.push_back( package.GetFullName() );
229 }
230 }
231
232 files.Sort();
233
234 unsigned long uncompressedBytes = 0;
235
236 // Our filename collector can store duplicate filenames. for instance *.gm2
237 // matches both *.g?? and *.gm??.
238 // So skip duplicate filenames (they are sorted, so it is easy.
239 wxString lastStoredFile;
240
241 for( unsigned ii = 0; ii < files.GetCount(); ii++ )
242 {
243 if( lastStoredFile == files[ii] ) // duplicate name: already stored
244 continue;
245
246 lastStoredFile = files[ii];
247
248 wxFileSystem fsfile;
249
250 wxFileName curr_fn( files[ii] );
251 curr_fn.MakeRelativeTo( aSrcDir );
252 currFilename = curr_fn.GetFullPath();
253
254 // Read input file and add it to the zip file:
255 wxFSFile* infile = fsfile.OpenFile( wxFileSystem::FileNameToURL( curr_fn ) );
256
257 if( infile )
258 {
259 zipstream.PutNextEntry( currFilename, infile->GetModificationTime() );
260 infile->GetStream()->Read( zipstream );
261 zipstream.CloseEntry();
262
263 uncompressedBytes += infile->GetStream()->GetSize();
264
265 if( aVerbose )
266 {
267 msg.Printf( _( "Archived file '%s'." ), currFilename );
268 aReporter.Report( msg, RPT_SEVERITY_INFO );
269 }
270
271 delete infile;
272 }
273 else
274 {
275 if( aVerbose )
276 {
277 msg.Printf( _( "Failed to archive file '%s'." ), currFilename );
278 aReporter.Report( msg, RPT_SEVERITY_ERROR );
279 }
280
281 success = false;
282 }
283 }
284
285 auto reportSize =
286 []( unsigned long aSize ) -> wxString
287 {
288 constexpr float KB = 1024.0;
289 constexpr float MB = KB * 1024.0;
290
291 if( aSize >= MB )
292 return wxString::Format( wxT( "%0.2f MB" ), aSize / MB );
293 else if( aSize >= KB )
294 return wxString::Format( wxT( "%0.2f KB" ), aSize / KB );
295 else
296 return wxString::Format( wxT( "%lu bytes" ), aSize );
297 };
298
299 size_t zipBytesCnt = ostream.GetSize();
300
301 if( zipstream.Close() )
302 {
303 msg.Printf( _( "Zip archive '%s' created (%s uncompressed, %s compressed)." ),
304 aDestFile,
305 reportSize( uncompressedBytes ),
306 reportSize( zipBytesCnt ) );
307 aReporter.Report( msg, RPT_SEVERITY_INFO );
308 }
309 else
310 {
311 msg.Printf( wxT( "Failed to create file '%s'." ), aDestFile );
312 aReporter.Report( msg, RPT_SEVERITY_ERROR );
313 success = false;
314 }
315
316 wxSetWorkingDirectory( oldCwd );
317 return success;
318}
constexpr std::size_t arrayDim(T const (&)[N]) noexcept
Returns # of elements in an array.
Definition: arraydim.h:31
static bool Archive(const wxString &aSrcDir, const wxString &aDestFile, REPORTER &aReporter, bool aVerbose=true, bool aIncludeExtraFiles=false)
Creates an archive of the project.
static bool Unarchive(const wxString &aSrcFile, const wxString &aDestDir, REPORTER &aReporter)
Extracts an archive of the current project over existing files Warning: this will overwrite files in ...
static bool AreZipArchivesIdentical(const wxString &aZipFileA, const wxString &aZipFileB, REPORTER &aReporter)
Compares 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)
This file contains miscellaneous commonly used macros and functions.
@ RPT_SEVERITY_ERROR
@ RPT_SEVERITY_INFO
static const std::vector< std::string > extensionList
Definition of file extensions used in Kicad.
static bool CopyStreamData(wxInputStream &inputStream, wxOutputStream &outputStream, wxFileOffset size)