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.

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