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 
33 
34 #define ZipFileExtension wxT( "zip" )
35 
36 
38 {
39 }
40 
41 
42 // Unarchive Files code comes from wxWidgets sample/archive/archive.cpp
43 static bool CopyStreamData( wxInputStream& inputStream, wxOutputStream& outputStream,
44  wxFileOffset size )
45 {
46  wxChar buf[128 * 1024];
47  int readSize = 128 * 1024;
48  wxFileOffset copiedData = 0;
49 
50  for( ; ; )
51  {
52  if(size != -1 && copiedData + readSize > size )
53  readSize = size - copiedData;
54 
55  inputStream.Read( buf, readSize );
56 
57  size_t actuallyRead = inputStream.LastRead();
58  outputStream.Write( buf, actuallyRead );
59 
60  if( outputStream.LastWrite() != actuallyRead )
61  {
62  wxLogError( "Failed to output data" );
63  //return false;
64  }
65 
66  if( size == -1 )
67  {
68  if( inputStream.Eof() )
69  break;
70  }
71  else
72  {
73  copiedData += actuallyRead;
74  if( copiedData >= size )
75  break;
76  }
77  }
78 
79  return true;
80 }
81 
82 
83 bool PROJECT_ARCHIVER::Unarchive( const wxString& aSrcFile, const wxString& aDestDir,
84  REPORTER& aReporter )
85 {
86  wxFFileInputStream stream( aSrcFile );
87 
88  if( !stream.IsOk() )
89  {
90  aReporter.Report( _( "Could not open archive file\n" ), RPT_SEVERITY_ERROR );
91  return false;
92  }
93 
94  const wxArchiveClassFactory* archiveClassFactory =
95  wxArchiveClassFactory::Find( aSrcFile, wxSTREAM_FILEEXT );
96 
97  if( !archiveClassFactory )
98  {
99  aReporter.Report( _( "Invalid archive file format\n" ), RPT_SEVERITY_ERROR );
100  return false;
101  }
102 
103  wxScopedPtr<wxArchiveInputStream> archiveStream( archiveClassFactory->NewStream( stream ) );
104 
105  wxString fileStatus;
106 
107  for( wxArchiveEntry* entry = archiveStream->GetNextEntry(); entry;
108  entry = archiveStream->GetNextEntry() )
109  {
110  fileStatus.Printf( _( "Extracting file \"%s\"\n" ), entry->GetName() );
111  aReporter.Report( fileStatus, RPT_SEVERITY_INFO );
112 
113  wxString fullname = aDestDir + entry->GetName();
114 
115  // Ensure the target directory exists and created it if not
116  wxString t_path = wxPathOnly( fullname );
117 
118  if( !wxDirExists( t_path ) )
119  {
120  // To create t_path, we need to create all subdirs from unzipDir
121  // to t_path.
122  wxFileName pathToCreate;
123  pathToCreate.AssignDir( t_path );
124  pathToCreate.MakeRelativeTo( aDestDir );
125 
126  // Create the list of subdirs candidates
127  wxArrayString subDirs;
128  subDirs = pathToCreate.GetDirs();
129  pathToCreate.AssignDir( aDestDir );
130 
131  for( size_t ii = 0; ii < subDirs.Count(); ii++ )
132  {
133  pathToCreate.AppendDir( subDirs[ii] );
134  wxString currPath = pathToCreate.GetPath();
135 
136  if( !wxDirExists( currPath ) )
137  wxMkdir( currPath );
138  }
139  }
140 
141  wxTempFileOutputStream outputFileStream( fullname );
142 
143  if( CopyStreamData( *archiveStream, outputFileStream, entry->GetSize() ) )
144  outputFileStream.Commit();
145  else
146  aReporter.Report( _( "Error extracting file!\n" ), RPT_SEVERITY_ERROR );
147  }
148 
149  aReporter.Report( wxT( "Extracted project\n" ), RPT_SEVERITY_INFO );
150  return true;
151 }
152 
153 
154 bool PROJECT_ARCHIVER::Archive( const wxString& aSrcDir, const wxString& aDestFile,
155  REPORTER& aReporter, bool aVerbose, bool aIncludeExtraFiles )
156 {
157  // List of file extensions that are always archived
158  static const wxChar* extensionList[] = {
159  wxT( "*.kicad_pro" ),
160  wxT( "*.kicad_prl" ),
161  wxT( "*.kicad_sch" ),
162  wxT( "*.kicad_sym" ),
163  wxT( "*.kicad_pcb" ),
164  wxT( "*.kicad_mod" ),
165  wxT( "*.kicad_dru" ),
166  wxT( "*.kicad_wks" ),
167  wxT( "fp-lib-table" ),
168  wxT( "sym-lib-table" )
169  };
170 
171  // List of additional file extensions that are only archived when aIncludeExtraFiles is true
172  static const wxChar* extraExtensionList[] = {
173  wxT( "*.pro" ),
174  wxT( "*.sch" ), // Legacy schematic files
175  wxT( "*.lib" ), wxT( "*.dcm" ), // Legacy schematic library files
176  wxT( "*.cmp" ),
177  wxT( "*.brd" ),
178  wxT( "*.mod" ),
179  wxT( "*.stp" ), wxT( "*.step" ), // 3d files
180  wxT( "*.wrl" ),
181  wxT( "*.gb?" ), wxT( "*.gbrjob" ), // Gerber files
182  wxT( "*.gko" ), wxT( "*.gm1" ),
183  wxT( "*.gm2" ), wxT( "*.g?" ),
184  wxT( "*.gp1" ), wxT( "*.gp2" ),
185  wxT( "*.gpb" ), wxT( "*.gpt" ),
186  wxT( "*.gt?" ),
187  wxT( "*.pos" ), wxT( "*.drl" ), wxT( "*.nc" ), wxT( "*.xnc" ), // Fab files
188  wxT( "*.d356" ), wxT( "*.rpt" ),
189  wxT( "*.net" ), wxT( "*.py" ),
190  wxT( "*.pdf" ), wxT( "*.txt" )
191  };
192 
193  bool success = true;
194  wxString msg;
195  wxString oldCwd = wxGetCwd();
196 
197  wxSetWorkingDirectory( aSrcDir );
198 
199  wxFFileOutputStream ostream( aDestFile );
200 
201  if( !ostream.IsOk() ) // issue to create the file. Perhaps not writable dir
202  {
203  msg.Printf( _( "Unable to create archive file \"%s\"\n" ), aDestFile );
204  aReporter.Report( msg, RPT_SEVERITY_ERROR );
205  return false;
206  }
207 
208  wxZipOutputStream zipstream( ostream, -1, wxConvUTF8 );
209 
210  // Build list of filenames to put in zip archive
211  wxString currFilename;
212 
213  wxArrayString files;
214 
215  for( unsigned ii = 0; ii < arrayDim( extensionList ); ii++ )
216  wxDir::GetAllFiles( aSrcDir, &files, extensionList[ii] );
217 
218  if( aIncludeExtraFiles )
219  {
220  for( unsigned ii = 0; ii < arrayDim( extraExtensionList ); ii++ )
221  wxDir::GetAllFiles( aSrcDir, &files, extraExtensionList[ii] );
222  }
223 
224  files.Sort();
225 
226  unsigned long uncompressedBytes = 0;
227 
228  for( unsigned ii = 0; ii < files.GetCount(); ii++ )
229  {
230  wxFileSystem fsfile;
231 
232  wxFileName curr_fn( files[ii] );
233  curr_fn.MakeRelativeTo( aSrcDir );
234  currFilename = curr_fn.GetFullPath();
235 
236  // Read input file and add it to the zip file:
237  wxFSFile* infile = fsfile.OpenFile( wxFileSystem::FileNameToURL( curr_fn ) );
238 
239  if( infile )
240  {
241  zipstream.PutNextEntry( currFilename, infile->GetModificationTime() );
242  infile->GetStream()->Read( zipstream );
243  zipstream.CloseEntry();
244 
245  uncompressedBytes += infile->GetStream()->GetSize();
246 
247  if( aVerbose )
248  {
249  msg.Printf( _( "Archive file \"%s\"\n" ), currFilename );
250  aReporter.Report( msg, RPT_SEVERITY_INFO );
251  }
252 
253  delete infile;
254  }
255  else
256  {
257  if( aVerbose )
258  {
259  msg.Printf( _( "Archive file \"%s\": Failed!\n" ), currFilename );
260  aReporter.Report( msg, RPT_SEVERITY_ERROR );
261  }
262 
263  success = false;
264  }
265  }
266 
267  auto reportSize =
268  []( unsigned long aSize ) -> wxString
269  {
270  constexpr float KB = 1024.0;
271  constexpr float MB = KB * 1024.0;
272 
273  if( aSize >= MB )
274  return wxString::Format( wxT( "%0.2f MB" ), aSize / MB );
275  else if( aSize >= KB )
276  return wxString::Format( wxT( "%0.2f KB" ), aSize / KB );
277  else
278  return wxString::Format( wxT( "%lu bytes" ), aSize );
279  };
280 
281  size_t zipBytesCnt = ostream.GetSize();
282 
283  if( zipstream.Close() )
284  {
285  msg.Printf( _( "Zip archive \"%s\" created (%s uncompressed, %s compressed)\n" ), aDestFile,
286  reportSize( uncompressedBytes ), reportSize( zipBytesCnt ) );
287  aReporter.Report( msg, RPT_SEVERITY_INFO );
288  }
289  else
290  {
291  msg.Printf( wxT( "Unable to create archive \"%s\"\n" ), aDestFile );
292  aReporter.Report( msg, RPT_SEVERITY_ERROR );
293  success = false;
294  }
295 
296  wxSetWorkingDirectory( oldCwd );
297  return success;
298 }
bool Archive(const wxString &aSrcDir, const wxString &aDestFile, REPORTER &aReporter, bool aVerbose=true, bool aIncludeExtraFiles=false)
Creates an archive of the project.
static const std::vector< std::string > extensionList
A pure virtual class used to derive REPORTER objects from.
Definition: reporter.h:64
virtual REPORTER & Report(const wxString &aText, SEVERITY aSeverity=RPT_SEVERITY_UNDEFINED)=0
Report a string with a given severity.
This file contains miscellaneous commonly used macros and functions.
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 ...
Definition of file extensions used in Kicad.
constexpr std::size_t arrayDim(T const (&)[N]) noexcept
Returns # of elements in an array.
Definition: arraydim.h:31
static bool CopyStreamData(wxInputStream &inputStream, wxOutputStream &outputStream, wxFileOffset size)
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
#define _(s)
Definition: 3d_actions.cpp:33