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.

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