You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

271 lines
8.5 KiB

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