/*
* 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
#include
#define ZipFileExtension wxT( "zip" )
class PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER : public wxDirTraverser
{
public:
PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER( const wxString& aPrjDir ) :
m_prjDir( aPrjDir )
{}
virtual wxDirTraverseResult OnFile( const wxString& aFilename ) override
{
m_files.emplace_back( aFilename );
return wxDIR_CONTINUE;
}
virtual wxDirTraverseResult OnDir( const wxString& aDirname ) override
{
return wxDIR_CONTINUE;
}
const std::vector& GetFilesToArchive() const
{
return m_files;
}
private:
wxString m_prjDir;
std::vector m_files;
};
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 )
{
std::set extensions;
std::set files; // File names without extensions such as fp-lib-table.
extensions.emplace( FILEEXT::ProjectFileExtension );
extensions.emplace( FILEEXT::ProjectLocalSettingsFileExtension );
extensions.emplace( FILEEXT::KiCadSchematicFileExtension );
extensions.emplace( FILEEXT::KiCadSymbolLibFileExtension );
extensions.emplace( FILEEXT::KiCadPcbFileExtension );
extensions.emplace( FILEEXT::KiCadFootprintFileExtension );
extensions.emplace( FILEEXT::DesignRulesFileExtension );
extensions.emplace( FILEEXT::DrawingSheetFileExtension );
extensions.emplace( FILEEXT::KiCadJobSetFileExtension );
extensions.emplace( FILEEXT::JsonFileExtension ); // for design blocks
extensions.emplace( FILEEXT::WorkbookFileExtension );
files.emplace( FILEEXT::FootprintLibraryTableFileName );
files.emplace( FILEEXT::SymbolLibraryTableFileName );
files.emplace( FILEEXT::DesignBlockLibraryTableFileName );
// List of additional file extensions that are only archived when aIncludeExtraFiles is true
if( aIncludeExtraFiles )
{
extensions.emplace( FILEEXT::LegacyProjectFileExtension );
extensions.emplace( FILEEXT::LegacySchematicFileExtension );
extensions.emplace( FILEEXT::LegacySymbolLibFileExtension );
extensions.emplace( FILEEXT::LegacySymbolDocumentFileExtension );
extensions.emplace( FILEEXT::FootprintAssignmentFileExtension );
extensions.emplace( FILEEXT::LegacyPcbFileExtension );
extensions.emplace( FILEEXT::LegacyFootprintLibPathExtension );
extensions.emplace( FILEEXT::StepFileAbrvExtension );
extensions.emplace( FILEEXT::StepFileExtension ); // 3d files
extensions.emplace( FILEEXT::VrmlFileExtension ); // 3d files
extensions.emplace( FILEEXT::GerberJobFileExtension ); // Gerber job files
extensions.emplace( FILEEXT::FootprintPlaceFileExtension ); // Our position files
extensions.emplace( FILEEXT::DrillFileExtension ); // Fab drill files
extensions.emplace( "nc" ); // Fab drill files
extensions.emplace( "xnc" ); // Fab drill files
extensions.emplace( FILEEXT::IpcD356FileExtension );
extensions.emplace( FILEEXT::ReportFileExtension );
extensions.emplace( FILEEXT::NetlistFileExtension );
extensions.emplace( FILEEXT::PythonFileExtension );
extensions.emplace( FILEEXT::PdfFileExtension );
extensions.emplace( FILEEXT::TextFileExtension );
extensions.emplace( FILEEXT::SpiceFileExtension ); // SPICE files
extensions.emplace( FILEEXT::SpiceSubcircuitFileExtension ); // SPICE files
extensions.emplace( FILEEXT::SpiceModelFileExtension ); // SPICE files
extensions.emplace( FILEEXT::IbisFileExtension );
extensions.emplace( "pkg" );
extensions.emplace( FILEEXT::GencadFileExtension );
}
// Gerber files (g?, g??, .gm12 (from protel export)).
wxRegEx gerberFiles( FILEEXT::GerberFileExtensionsRegex );
wxASSERT( gerberFiles.IsValid() );
bool success = true;
wxString msg;
wxString oldCwd = wxGetCwd();
wxFileName sourceDir( aSrcDir, wxEmptyString, wxEmptyString );
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 );
wxDir projectDir( aSrcDir );
if( !projectDir.IsOpened() )
{
if( aVerbose )
{
msg.Printf( _( "Error opening directory: '%s'." ), aSrcDir );
aReporter.Report( msg, RPT_SEVERITY_ERROR );
}
wxSetWorkingDirectory( oldCwd );
return false;
}
size_t uncompressedBytes = 0;
PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER traverser( aSrcDir );
projectDir.Traverse( traverser );
for( const wxString& fileName : traverser.GetFilesToArchive() )
{
wxFileName fn( fileName );
wxString extLower = fn.GetExt().Lower();
wxString fileNameLower = fn.GetName().Lower();
bool archive = false;
if( !extLower.IsEmpty() )
{
if( ( extensions.find( extLower ) != extensions.end() )
|| ( aIncludeExtraFiles && gerberFiles.Matches( extLower ) ) )
archive = true;
}
else if( !fileNameLower.IsEmpty() && ( files.find( fileNameLower ) != files.end() ) )
{
archive = true;
}
if( !archive )
continue;
wxFileSystem fsFile;
fn.MakeRelativeTo( aSrcDir );
wxString relativeFn = fn.GetFullPath();
// Read input file and add it to the zip file:
wxFSFile* infile = fsFile.OpenFile( relativeFn );
if( infile )
{
zipstream.PutNextEntry( relativeFn, infile->GetModificationTime() );
infile->GetStream()->Read( zipstream );
zipstream.CloseEntry();
uncompressedBytes += infile->GetStream()->GetSize();
if( aVerbose )
{
msg.Printf( _( "Archived file '%s'." ), relativeFn );
aReporter.Report( msg, RPT_SEVERITY_INFO );
}
delete infile;
}
else
{
if( aVerbose )
{
msg.Printf( _( "Failed to archive file '%s'." ), relativeFn );
aReporter.Report( msg, RPT_SEVERITY_ERROR );
}
}
}
auto reportSize =
[]( size_t 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( "%zu 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;
}