/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2020 KiCad Developers, see AUTHORS.txt for contributors. * * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the * Free Software Foundation, either version 3 of the License, or (at your * option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ZipFileExtension wxT( "zip" ) PROJECT_ARCHIVER::PROJECT_ARCHIVER() { } // Unarchive Files code comes from wxWidgets sample/archive/archive.cpp bool PROJECT_ARCHIVER::Unarchive( const wxString& aSrcFile, const wxString& aDestDir, REPORTER& aReporter ) { wxFFileInputStream stream( aSrcFile ); if( !stream.IsOk() ) { aReporter.Report( _( "Could not open archive file." ), RPT_SEVERITY_ERROR ); return false; } const wxArchiveClassFactory* archiveClassFactory = wxArchiveClassFactory::Find( aSrcFile, wxSTREAM_FILEEXT ); if( !archiveClassFactory ) { aReporter.Report( _( "Invalid archive file format." ), RPT_SEVERITY_ERROR ); return false; } std::unique_ptr archiveStream( archiveClassFactory->NewStream( stream ) ); wxString fileStatus; for( wxArchiveEntry* entry = archiveStream->GetNextEntry(); entry; entry = archiveStream->GetNextEntry() ) { fileStatus.Printf( _( "Extracting file '%s'." ), entry->GetName() ); aReporter.Report( fileStatus, RPT_SEVERITY_INFO ); wxString fullname = aDestDir + entry->GetName(); // Ensure the target directory exists and create it if not wxString t_path = wxPathOnly( fullname ); if( !wxDirExists( t_path ) ) { wxFileName::Mkdir( t_path, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ); } // Directory entries need only be created, not extracted (0 size) if( entry->IsDir() ) continue; wxTempFileOutputStream outputFileStream( fullname ); if( CopyStreamData( *archiveStream, outputFileStream, entry->GetSize() ) ) outputFileStream.Commit(); else aReporter.Report( _( "Error extracting file!" ), RPT_SEVERITY_ERROR ); // Now let's set the filetimes based on what's in the zip wxFileName outputFileName( fullname ); wxDateTime fileTime = entry->GetDateTime(); // For now we set access, mod, create to the same datetime // create (third arg) is only used on Windows outputFileName.SetTimes( &fileTime, &fileTime, &fileTime ); } aReporter.Report( wxT( "Extracted project." ), RPT_SEVERITY_INFO ); return true; } bool PROJECT_ARCHIVER::Archive( const wxString& aSrcDir, const wxString& aDestFile, REPORTER& aReporter, bool aVerbose, bool aIncludeExtraFiles ) { // List of file extensions that are always archived static const wxChar* extensionList[] = { wxT( "*.kicad_pro" ), wxT( "*.kicad_prl" ), wxT( "*.kicad_sch" ), wxT( "*.kicad_sym" ), wxT( "*.kicad_pcb" ), wxT( "*.kicad_mod" ), wxT( "*.kicad_dru" ), wxT( "*.kicad_wks" ), wxT( "*.wbk" ), wxT( "fp-lib-table" ), wxT( "sym-lib-table" ) }; // List of additional file extensions that are only archived when aIncludeExtraFiles is true static const wxChar* extraExtensionList[] = { wxT( "*.pro" ), // Legacy project files wxT( "*.sch" ), // Legacy schematic files wxT( "*.lib" ), wxT( "*.dcm" ), // Legacy schematic library files wxT( "*.cmp" ), wxT( "*.brd" ), // Legacy PCB files wxT( "*.mod" ), // Legacy footprint library files wxT( "*.stp" ), wxT( "*.step" ), // 3d files wxT( "*.wrl" ), wxT( "*.g?" ), wxT( "*.g??" ), // Gerber files wxT( "*.gm??"), // Some gerbers like .gm12 (from protel export) wxT( "*.gbrjob" ), // Gerber job files wxT( "*.pos" ), // our position files wxT( "*.drl" ), wxT( "*.nc" ), wxT( "*.xnc" ), // Fab drill files wxT( "*.d356" ), wxT( "*.rpt" ), wxT( "*.net" ), wxT( "*.py" ), wxT( "*.pdf" ), wxT( "*.txt" ), wxT( "*.cir" ), wxT( "*.sub" ), wxT( "*.model" ), // SPICE files wxT( "*.ibs" ) }; bool success = true; wxString msg; wxString oldCwd = wxGetCwd(); wxSetWorkingDirectory( aSrcDir ); wxFFileOutputStream ostream( aDestFile ); if( !ostream.IsOk() ) // issue to create the file. Perhaps not writable dir { msg.Printf( _( "Failed to create file '%s'." ), aDestFile ); aReporter.Report( msg, RPT_SEVERITY_ERROR ); return false; } wxZipOutputStream zipstream( ostream, -1, wxConvUTF8 ); // Build list of filenames to put in zip archive wxString currFilename; wxArrayString files; for( unsigned ii = 0; ii < arrayDim( extensionList ); ii++ ) wxDir::GetAllFiles( aSrcDir, &files, extensionList[ii] ); if( aIncludeExtraFiles ) { for( unsigned ii = 0; ii < arrayDim( extraExtensionList ); ii++ ) wxDir::GetAllFiles( aSrcDir, &files, extraExtensionList[ii] ); } for( unsigned ii = 0; ii < files.GetCount(); ++ii ) { if( files[ii].EndsWith( wxS( ".ibs" ) ) ) { wxFileName package( files[ ii ] ); package.MakeRelativeTo( aSrcDir ); package.SetExt( wxS( "pkg" ) ); if( package.Exists() ) files.push_back( package.GetFullName() ); } } files.Sort(); unsigned long uncompressedBytes = 0; // Our filename collector can store duplicate filenames. for instance *.gm2 // matches both *.g?? and *.gm??. // So skip duplicate filenames (they are sorted, so it is easy. wxString lastStoredFile; for( unsigned ii = 0; ii < files.GetCount(); ii++ ) { if( lastStoredFile == files[ii] ) // duplicate name: already stored continue; lastStoredFile = files[ii]; wxFileSystem fsfile; wxFileName curr_fn( files[ii] ); curr_fn.MakeRelativeTo( aSrcDir ); currFilename = curr_fn.GetFullPath(); // Read input file and add it to the zip file: wxFSFile* infile = fsfile.OpenFile( wxFileSystem::FileNameToURL( curr_fn ) ); if( infile ) { zipstream.PutNextEntry( currFilename, infile->GetModificationTime() ); infile->GetStream()->Read( zipstream ); zipstream.CloseEntry(); uncompressedBytes += infile->GetStream()->GetSize(); if( aVerbose ) { msg.Printf( _( "Archived file '%s'." ), currFilename ); aReporter.Report( msg, RPT_SEVERITY_INFO ); } delete infile; } else { if( aVerbose ) { msg.Printf( _( "Failed to archive file '%s'." ), currFilename ); aReporter.Report( msg, RPT_SEVERITY_ERROR ); } success = false; } } auto reportSize = []( unsigned long aSize ) -> wxString { constexpr float KB = 1024.0; constexpr float MB = KB * 1024.0; if( aSize >= MB ) return wxString::Format( wxT( "%0.2f MB" ), aSize / MB ); else if( aSize >= KB ) return wxString::Format( wxT( "%0.2f KB" ), aSize / KB ); else return wxString::Format( wxT( "%lu bytes" ), aSize ); }; size_t zipBytesCnt = ostream.GetSize(); if( zipstream.Close() ) { msg.Printf( _( "Zip archive '%s' created (%s uncompressed, %s compressed)." ), aDestFile, reportSize( uncompressedBytes ), reportSize( zipBytesCnt ) ); aReporter.Report( msg, RPT_SEVERITY_INFO ); } else { msg.Printf( wxT( "Failed to create file '%s'." ), aDestFile ); aReporter.Report( msg, RPT_SEVERITY_ERROR ); success = false; } wxSetWorkingDirectory( oldCwd ); return success; }