KiCad PCB EDA Suite
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 <wx/dir.h>
21#include <wx/filedlg.h>
22#include <wx/fs_zip.h>
23#include <wx/uri.h>
24#include <wx/wfstream.h>
25#include <wx/zipstrm.h>
26
27#include <core/arraydim.h>
28#include <macros.h>
30#include <reporter.h>
32#include <wxstream_helper.h>
33#include <wx/log.h>
34
35
36#define ZipFileExtension wxT( "zip" )
37
38
40{
41}
42
43
44// Unarchive Files code comes from wxWidgets sample/archive/archive.cpp
45bool PROJECT_ARCHIVER::Unarchive( const wxString& aSrcFile, const wxString& aDestDir,
46 REPORTER& aReporter )
47{
48 wxFFileInputStream stream( aSrcFile );
49
50 if( !stream.IsOk() )
51 {
52 aReporter.Report( _( "Could not open archive file." ), RPT_SEVERITY_ERROR );
53 return false;
54 }
55
56 const wxArchiveClassFactory* archiveClassFactory =
57 wxArchiveClassFactory::Find( aSrcFile, wxSTREAM_FILEEXT );
58
59 if( !archiveClassFactory )
60 {
61 aReporter.Report( _( "Invalid archive file format." ), RPT_SEVERITY_ERROR );
62 return false;
63 }
64
65 wxScopedPtr<wxArchiveInputStream> archiveStream( archiveClassFactory->NewStream( stream ) );
66
67 wxString fileStatus;
68
69 for( wxArchiveEntry* entry = archiveStream->GetNextEntry(); entry;
70 entry = archiveStream->GetNextEntry() )
71 {
72 fileStatus.Printf( _( "Extracting file '%s'." ), entry->GetName() );
73 aReporter.Report( fileStatus, RPT_SEVERITY_INFO );
74
75 wxString fullname = aDestDir + entry->GetName();
76
77 // Ensure the target directory exists and create it if not
78 wxString t_path = wxPathOnly( fullname );
79
80 if( !wxDirExists( t_path ) )
81 {
82 wxFileName::Mkdir( t_path, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
83 }
84
85 // Directory entries need only be created, not extracted (0 size)
86 if( entry->IsDir() )
87 continue;
88
89
90 wxTempFileOutputStream outputFileStream( fullname );
91
92 if( CopyStreamData( *archiveStream, outputFileStream, entry->GetSize() ) )
93 outputFileStream.Commit();
94 else
95 aReporter.Report( _( "Error extracting file!" ), RPT_SEVERITY_ERROR );
96
97 // Now let's set the filetimes based on what's in the zip
98 wxFileName outputFileName( fullname );
99 wxDateTime fileTime = entry->GetDateTime();
100 // For now we set access, mod, create to the same datetime
101 // create (third arg) is only used on Windows
102 outputFileName.SetTimes( &fileTime, &fileTime, &fileTime );
103 }
104
105 aReporter.Report( wxT( "Extracted project." ), RPT_SEVERITY_INFO );
106 return true;
107}
108
109
110bool PROJECT_ARCHIVER::Archive( const wxString& aSrcDir, const wxString& aDestFile,
111 REPORTER& aReporter, bool aVerbose, bool aIncludeExtraFiles )
112{
113 // List of file extensions that are always archived
114 static const wxChar* extensionList[] = {
115 wxT( "*.kicad_pro" ),
116 wxT( "*.kicad_prl" ),
117 wxT( "*.kicad_sch" ),
118 wxT( "*.kicad_sym" ),
119 wxT( "*.kicad_pcb" ),
120 wxT( "*.kicad_mod" ),
121 wxT( "*.kicad_dru" ),
122 wxT( "*.kicad_wks" ),
123 wxT( "fp-lib-table" ),
124 wxT( "sym-lib-table" )
125 };
126
127 // List of additional file extensions that are only archived when aIncludeExtraFiles is true
128 static const wxChar* extraExtensionList[] = {
129 wxT( "*.pro" ),
130 wxT( "*.sch" ), // Legacy schematic files
131 wxT( "*.lib" ), wxT( "*.dcm" ), // Legacy schematic library files
132 wxT( "*.cmp" ),
133 wxT( "*.brd" ),
134 wxT( "*.mod" ),
135 wxT( "*.stp" ), wxT( "*.step" ), // 3d files
136 wxT( "*.wrl" ),
137 wxT( "*.g?" ), wxT( "*.g??" ), // Gerber files
138 wxT( "*.gm??" ), wxT( "*.gbrjob" ),
139 wxT( "*.pos" ), wxT( "*.drl" ), wxT( "*.nc" ), wxT( "*.xnc" ), // Fab files
140 wxT( "*.d356" ),
141 wxT( "*.rpt" ),
142 wxT( "*.net" ),
143 wxT( "*.py" ),
144 wxT( "*.pdf" ),
145 wxT( "*.txt" ),
146 wxT( "*.cir" ), wxT( "*.sub" ), wxT( "*.model" ), // SPICE files
147 wxT( "*.ibs" )
148 };
149
150 bool success = true;
151 wxString msg;
152 wxString oldCwd = wxGetCwd();
153
154 wxSetWorkingDirectory( aSrcDir );
155
156 wxFFileOutputStream ostream( aDestFile );
157
158 if( !ostream.IsOk() ) // issue to create the file. Perhaps not writable dir
159 {
160 msg.Printf( _( "Failed to create file '%s'." ), aDestFile );
161 aReporter.Report( msg, RPT_SEVERITY_ERROR );
162 return false;
163 }
164
165 wxZipOutputStream zipstream( ostream, -1, wxConvUTF8 );
166
167 // Build list of filenames to put in zip archive
168 wxString currFilename;
169
170 wxArrayString files;
171
172 for( unsigned ii = 0; ii < arrayDim( extensionList ); ii++ )
173 wxDir::GetAllFiles( aSrcDir, &files, extensionList[ii] );
174
175 if( aIncludeExtraFiles )
176 {
177 for( unsigned ii = 0; ii < arrayDim( extraExtensionList ); ii++ )
178 wxDir::GetAllFiles( aSrcDir, &files, extraExtensionList[ii] );
179 }
180
181 for( unsigned ii = 0; ii < files.GetCount(); ++ii )
182 {
183 if( files[ii].EndsWith( wxS( ".ibs" ) ) )
184 {
185 wxFileName package( files[ ii ] );
186 package.MakeRelativeTo( aSrcDir );
187 package.SetExt( wxS( "pkg" ) );
188
189 if( package.Exists() )
190 files.push_back( package.GetFullName() );
191 }
192 }
193
194 files.Sort();
195
196 unsigned long uncompressedBytes = 0;
197
198 for( unsigned ii = 0; ii < files.GetCount(); ii++ )
199 {
200 wxFileSystem fsfile;
201
202 wxFileName curr_fn( files[ii] );
203 curr_fn.MakeRelativeTo( aSrcDir );
204 currFilename = curr_fn.GetFullPath();
205
206 // Read input file and add it to the zip file:
207 wxFSFile* infile = fsfile.OpenFile( wxFileSystem::FileNameToURL( curr_fn ) );
208
209 if( infile )
210 {
211 zipstream.PutNextEntry( currFilename, infile->GetModificationTime() );
212 infile->GetStream()->Read( zipstream );
213 zipstream.CloseEntry();
214
215 uncompressedBytes += infile->GetStream()->GetSize();
216
217 if( aVerbose )
218 {
219 msg.Printf( _( "Archived file '%s'." ), currFilename );
220 aReporter.Report( msg, RPT_SEVERITY_INFO );
221 }
222
223 delete infile;
224 }
225 else
226 {
227 if( aVerbose )
228 {
229 msg.Printf( _( "Failed to archive file '%s'." ), currFilename );
230 aReporter.Report( msg, RPT_SEVERITY_ERROR );
231 }
232
233 success = false;
234 }
235 }
236
237 auto reportSize =
238 []( unsigned long aSize ) -> wxString
239 {
240 constexpr float KB = 1024.0;
241 constexpr float MB = KB * 1024.0;
242
243 if( aSize >= MB )
244 return wxString::Format( wxT( "%0.2f MB" ), aSize / MB );
245 else if( aSize >= KB )
246 return wxString::Format( wxT( "%0.2f KB" ), aSize / KB );
247 else
248 return wxString::Format( wxT( "%lu bytes" ), aSize );
249 };
250
251 size_t zipBytesCnt = ostream.GetSize();
252
253 if( zipstream.Close() )
254 {
255 msg.Printf( _( "Zip archive '%s' created (%s uncompressed, %s compressed)." ),
256 aDestFile,
257 reportSize( uncompressedBytes ),
258 reportSize( zipBytesCnt ) );
259 aReporter.Report( msg, RPT_SEVERITY_INFO );
260 }
261 else
262 {
263 msg.Printf( wxT( "Failed to create file '%s'." ), aDestFile );
264 aReporter.Report( msg, RPT_SEVERITY_ERROR );
265 success = false;
266 }
267
268 wxSetWorkingDirectory( oldCwd );
269 return success;
270}
constexpr std::size_t arrayDim(T const (&)[N]) noexcept
Returns # of elements in an array.
Definition: arraydim.h:31
bool Archive(const wxString &aSrcDir, const wxString &aDestFile, REPORTER &aReporter, bool aVerbose=true, bool aIncludeExtraFiles=false)
Creates an archive of the project.
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 ...
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:71
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.
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
@ 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)