/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The 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
#include
#include
#include
#define ZipFileExtension wxT( "zip" )
class PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER : public wxDirTraverser
{
public:
PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER( const std::string& aExtRegex, const wxString& aPrjDir, wxZipOutputStream& aZipFileOutput,
REPORTER& aReporter, bool aVerbose ) :
m_zipFile( aZipFileOutput ),
m_prjDir( aPrjDir ),
m_fileExtRegex( aExtRegex, std::regex_constants::ECMAScript | std::regex_constants::icase ),
m_reporter( aReporter ),
m_errorOccurred( false ),
m_verbose( aVerbose )
{}
virtual wxDirTraverseResult OnFile( const wxString& aFilename ) override
{
if( std::regex_search( aFilename.ToStdString(), m_fileExtRegex ) )
{
addFileToZip( aFilename );
// Special processing for IBIS files to include the corresponding pkg file
if( aFilename.EndsWith( FILEEXT::IbisFileExtension ) )
{
wxFileName package( aFilename );
package.MakeRelativeTo( m_prjDir );
package.SetExt( wxS( "pkg" ) );
if( package.Exists() )
addFileToZip( package.GetFullPath() );
}
}
return wxDIR_CONTINUE;
}
virtual wxDirTraverseResult OnDir( const wxString& aDirname ) override
{
return wxDIR_CONTINUE;
}
unsigned long GetUncompressedBytes() const
{
return m_uncompressedBytes;
}
bool GetErrorOccurred() const
{
return m_errorOccurred;
}
private:
void addFileToZip( const wxString& aFilename)
{
wxString msg;
wxFileSystem fsfile;
wxFileName curr_fn( aFilename );
curr_fn.MakeRelativeTo( m_prjDir );
wxString currFilename = curr_fn.GetFullPath();
// Read input file and add it to the zip file:
wxFSFile* infile = fsfile.OpenFile( currFilename );
if( infile )
{
m_zipFile.PutNextEntry( currFilename, infile->GetModificationTime() );
infile->GetStream()->Read( m_zipFile );
m_zipFile.CloseEntry();
m_uncompressedBytes += infile->GetStream()->GetSize();
if( m_verbose )
{
msg.Printf( _( "Archived file '%s'." ), currFilename );
m_reporter.Report( msg, RPT_SEVERITY_INFO );
}
delete infile;
}
else
{
if( m_verbose )
{
msg.Printf( _( "Failed to archive file '%s'." ), currFilename );
m_reporter.Report( msg, RPT_SEVERITY_ERROR );
}
m_errorOccurred = true;
}
}
private:
wxZipOutputStream& m_zipFile;
wxString m_prjDir;
std::regex m_fileExtRegex;
REPORTER& m_reporter;
bool m_errorOccurred; // True if an error archiving the file
bool m_verbose; // True to enable verbose logging
// Keep track of how many bytes would have been used without compression
unsigned long m_uncompressedBytes = 0;
};
PROJECT_ARCHIVER::PROJECT_ARCHIVER()
{
}
bool PROJECT_ARCHIVER::AreZipArchivesIdentical( const wxString& aZipFileA,
const wxString& aZipFileB, REPORTER& aReporter )
{
wxFFileInputStream streamA( aZipFileA );
wxFFileInputStream streamB( aZipFileB );
if( !streamA.IsOk() || !streamB.IsOk() )
{
aReporter.Report( _( "Could not open archive file." ), RPT_SEVERITY_ERROR );
return false;
}
wxZipInputStream zipStreamA = wxZipInputStream( streamA );
wxZipInputStream zipStreamB = wxZipInputStream( streamB );
std::set crcsA;
std::set crcsB;
for( wxZipEntry* entry = zipStreamA.GetNextEntry(); entry; entry = zipStreamA.GetNextEntry() )
{
crcsA.insert( entry->GetCrc() );
}
for( wxZipEntry* entry = zipStreamB.GetNextEntry(); entry; entry = zipStreamB.GetNextEntry() )
{
crcsB.insert( entry->GetCrc() );
}
return crcsA == crcsB;
}
// 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 )
{
#define EXT( ext ) "\\." + ext + "|"
#define NAME( name ) name + "|"
#define EXT_NO_PIPE( ext ) "\\." + ext
#define NAME_NO_PIPE( name ) name
// List of file extensions that are always archived
std::string fileExtensionRegex = "("
EXT( FILEEXT::ProjectFileExtension )
EXT( FILEEXT::ProjectLocalSettingsFileExtension )
EXT( FILEEXT::KiCadSchematicFileExtension )
EXT( FILEEXT::KiCadSymbolLibFileExtension )
EXT( FILEEXT::KiCadPcbFileExtension )
EXT( FILEEXT::KiCadFootprintFileExtension )
EXT( FILEEXT::DesignRulesFileExtension )
EXT( FILEEXT::DrawingSheetFileExtension )
EXT( FILEEXT::KiCadJobSetFileExtension )
EXT( FILEEXT::JsonFileExtension ) // for design blocks
EXT( FILEEXT::WorkbookFileExtension ) +
NAME( FILEEXT::FootprintLibraryTableFileName ) +
NAME( FILEEXT::SymbolLibraryTableFileName ) +
NAME_NO_PIPE( FILEEXT::DesignBlockLibraryTableFileName );
// List of additional file extensions that are only archived when aIncludeExtraFiles is true
if( aIncludeExtraFiles )
{
fileExtensionRegex += "|"
EXT( FILEEXT::LegacyProjectFileExtension )
EXT( FILEEXT::LegacySchematicFileExtension )
EXT( FILEEXT::LegacySymbolLibFileExtension )
EXT( FILEEXT::LegacySymbolDocumentFileExtension )
EXT( FILEEXT::FootprintAssignmentFileExtension )
EXT( FILEEXT::LegacyPcbFileExtension )
EXT( FILEEXT::LegacyFootprintLibPathExtension )
EXT( FILEEXT::StepFileAbrvExtension ) // 3d files
EXT( FILEEXT::StepFileExtension ) // 3d files
EXT( FILEEXT::VrmlFileExtension ) // 3d files
EXT( FILEEXT::GerberFileExtensionsRegex ) // Gerber files (g?, g??, .gm12 (from protel export))
EXT( FILEEXT::GerberJobFileExtension ) // Gerber job files
EXT( FILEEXT::FootprintPlaceFileExtension ) // Our position files
EXT( FILEEXT::DrillFileExtension ) // Fab drill files
EXT( "nc" ) // Fab drill files
EXT( "xnc" ) // Fab drill files
EXT( FILEEXT::IpcD356FileExtension )
EXT( FILEEXT::ReportFileExtension )
EXT( FILEEXT::NetlistFileExtension )
EXT( FILEEXT::PythonFileExtension )
EXT( FILEEXT::PdfFileExtension )
EXT( FILEEXT::TextFileExtension )
EXT( FILEEXT::SpiceFileExtension ) // SPICE files
EXT( FILEEXT::SpiceSubcircuitFileExtension ) // SPICE files
EXT( FILEEXT::SpiceModelFileExtension ) // SPICE files
EXT_NO_PIPE( FILEEXT::IbisFileExtension );
}
fileExtensionRegex += ")";
#undef EXT
#undef NAME
#undef EXT_NO_PIPE
#undef NAME_NO_PIPE
bool success = true;
wxString msg;
wxString oldCwd = wxGetCwd();
wxFileName sourceDir( aSrcDir );
wxSetWorkingDirectory( sourceDir.GetFullPath() );
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 );
wxDir projectDir( aSrcDir );
wxString currFilename;
wxArrayString files;
if( !projectDir.IsOpened() )
{
if( aVerbose )
{
msg.Printf( _( "Error opening directory: '%s'." ), aSrcDir );
aReporter.Report( msg, RPT_SEVERITY_ERROR );
}
wxSetWorkingDirectory( oldCwd );
return false;
}
try
{
PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER traverser( fileExtensionRegex, aSrcDir, zipstream,
aReporter, aVerbose );
projectDir.Traverse( traverser );
success = !traverser.GetErrorOccurred();
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();
unsigned long uncompressedBytes = traverser.GetUncompressedBytes();
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;
}
}
catch( const std::regex_error& e )
{
// Something bad happened here with the regex
wxASSERT_MSG( false, e.what() );
if( aVerbose )
{
msg.Printf( _( "Error: '%s'." ), e.what() );
aReporter.Report( msg, RPT_SEVERITY_ERROR );
}
success = false;
}
wxSetWorkingDirectory( oldCwd );
return success;
}