2020-07-03 13:40:31 +00:00
|
|
|
/*
|
|
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2023-04-12 16:14:45 +00:00
|
|
|
#include <memory>
|
2020-07-03 13:40:31 +00:00
|
|
|
#include <wx/dir.h>
|
|
|
|
#include <wx/filedlg.h>
|
|
|
|
#include <wx/fs_zip.h>
|
|
|
|
#include <wx/uri.h>
|
|
|
|
#include <wx/wfstream.h>
|
|
|
|
#include <wx/zipstrm.h>
|
|
|
|
|
2020-11-18 01:21:04 +00:00
|
|
|
#include <core/arraydim.h>
|
2020-07-03 13:40:31 +00:00
|
|
|
#include <macros.h>
|
|
|
|
#include <project/project_archiver.h>
|
|
|
|
#include <reporter.h>
|
|
|
|
#include <wildcards_and_files_ext.h>
|
2020-10-26 10:54:36 +00:00
|
|
|
#include <wxstream_helper.h>
|
2021-05-01 07:50:29 +00:00
|
|
|
#include <wx/log.h>
|
2020-07-03 13:40:31 +00:00
|
|
|
|
|
|
|
|
|
|
|
#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 )
|
|
|
|
{
|
2020-12-12 23:51:35 +00:00
|
|
|
wxFFileInputStream stream( aSrcFile );
|
2020-07-03 13:40:31 +00:00
|
|
|
|
2020-11-10 20:31:52 +00:00
|
|
|
if( !stream.IsOk() )
|
2020-07-03 13:40:31 +00:00
|
|
|
{
|
2021-07-05 12:40:38 +00:00
|
|
|
aReporter.Report( _( "Could not open archive file." ), RPT_SEVERITY_ERROR );
|
2020-07-03 13:40:31 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const wxArchiveClassFactory* archiveClassFactory =
|
|
|
|
wxArchiveClassFactory::Find( aSrcFile, wxSTREAM_FILEEXT );
|
|
|
|
|
|
|
|
if( !archiveClassFactory )
|
|
|
|
{
|
2021-07-05 12:40:38 +00:00
|
|
|
aReporter.Report( _( "Invalid archive file format." ), RPT_SEVERITY_ERROR );
|
2020-07-03 13:40:31 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2023-04-12 16:14:45 +00:00
|
|
|
std::unique_ptr<wxArchiveInputStream> archiveStream( archiveClassFactory->NewStream( stream ) );
|
2020-07-03 13:40:31 +00:00
|
|
|
|
|
|
|
wxString fileStatus;
|
|
|
|
|
|
|
|
for( wxArchiveEntry* entry = archiveStream->GetNextEntry(); entry;
|
|
|
|
entry = archiveStream->GetNextEntry() )
|
|
|
|
{
|
2021-07-05 12:40:38 +00:00
|
|
|
fileStatus.Printf( _( "Extracting file '%s'." ), entry->GetName() );
|
2020-07-03 13:40:31 +00:00
|
|
|
aReporter.Report( fileStatus, RPT_SEVERITY_INFO );
|
|
|
|
|
|
|
|
wxString fullname = aDestDir + entry->GetName();
|
|
|
|
|
2020-10-26 10:54:36 +00:00
|
|
|
// Ensure the target directory exists and create it if not
|
2020-07-03 13:40:31 +00:00
|
|
|
wxString t_path = wxPathOnly( fullname );
|
|
|
|
|
|
|
|
if( !wxDirExists( t_path ) )
|
|
|
|
{
|
2020-10-26 10:54:36 +00:00
|
|
|
wxFileName::Mkdir( t_path, wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL );
|
2020-07-03 13:40:31 +00:00
|
|
|
}
|
|
|
|
|
2021-05-28 22:41:21 +00:00
|
|
|
// Directory entries need only be created, not extracted (0 size)
|
|
|
|
if( entry->IsDir() )
|
|
|
|
continue;
|
|
|
|
|
2021-09-01 05:04:43 +00:00
|
|
|
|
2020-07-03 13:40:31 +00:00
|
|
|
wxTempFileOutputStream outputFileStream( fullname );
|
|
|
|
|
|
|
|
if( CopyStreamData( *archiveStream, outputFileStream, entry->GetSize() ) )
|
|
|
|
outputFileStream.Commit();
|
|
|
|
else
|
2021-07-05 12:40:38 +00:00
|
|
|
aReporter.Report( _( "Error extracting file!" ), RPT_SEVERITY_ERROR );
|
2021-09-01 05:04:43 +00:00
|
|
|
|
|
|
|
// 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 );
|
2020-07-03 13:40:31 +00:00
|
|
|
}
|
|
|
|
|
2021-07-05 12:40:38 +00:00
|
|
|
aReporter.Report( wxT( "Extracted project." ), RPT_SEVERITY_INFO );
|
2020-07-03 13:40:31 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool PROJECT_ARCHIVER::Archive( const wxString& aSrcDir, const wxString& aDestFile,
|
2021-02-04 22:24:02 +00:00
|
|
|
REPORTER& aReporter, bool aVerbose, bool aIncludeExtraFiles )
|
2020-07-03 13:40:31 +00:00
|
|
|
{
|
2021-02-04 22:24:02 +00:00
|
|
|
// List of file extensions that are always archived
|
2020-07-03 13:40:31 +00:00
|
|
|
static const wxChar* extensionList[] = {
|
|
|
|
wxT( "*.kicad_pro" ),
|
|
|
|
wxT( "*.kicad_prl" ),
|
2021-02-04 22:24:02 +00:00
|
|
|
wxT( "*.kicad_sch" ),
|
|
|
|
wxT( "*.kicad_sym" ),
|
|
|
|
wxT( "*.kicad_pcb" ),
|
|
|
|
wxT( "*.kicad_mod" ),
|
|
|
|
wxT( "*.kicad_dru" ),
|
|
|
|
wxT( "*.kicad_wks" ),
|
2023-08-06 20:56:11 +00:00
|
|
|
wxT( "*.wbk" ),
|
2021-02-04 22:24:02 +00:00
|
|
|
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[] = {
|
2023-08-06 20:56:11 +00:00
|
|
|
wxT( "*.pro" ), // Legacy project files
|
2020-07-03 13:40:31 +00:00
|
|
|
wxT( "*.sch" ), // Legacy schematic files
|
|
|
|
wxT( "*.lib" ), wxT( "*.dcm" ), // Legacy schematic library files
|
|
|
|
wxT( "*.cmp" ),
|
2023-08-06 20:56:11 +00:00
|
|
|
wxT( "*.brd" ), // Legacy PCB files
|
|
|
|
wxT( "*.mod" ), // Legacy footprint library files
|
2021-02-04 22:24:02 +00:00
|
|
|
wxT( "*.stp" ), wxT( "*.step" ), // 3d files
|
|
|
|
wxT( "*.wrl" ),
|
2022-11-17 23:39:18 +00:00
|
|
|
wxT( "*.g?" ), wxT( "*.g??" ), // Gerber files
|
2023-11-21 08:24:14 +00:00
|
|
|
wxT( "*.gm??"), // Some gerbers like .gm12 (from protel export)
|
2023-11-19 18:54:58 +00:00
|
|
|
wxT( "*.gbrjob" ), // Gerber job files
|
2023-11-21 08:24:14 +00:00
|
|
|
wxT( "*.pos" ), // our position files
|
|
|
|
wxT( "*.drl" ), wxT( "*.nc" ), wxT( "*.xnc" ), // Fab drill files
|
2023-02-13 22:43:16 +00:00
|
|
|
wxT( "*.d356" ),
|
|
|
|
wxT( "*.rpt" ),
|
|
|
|
wxT( "*.net" ),
|
|
|
|
wxT( "*.py" ),
|
|
|
|
wxT( "*.pdf" ),
|
|
|
|
wxT( "*.txt" ),
|
2023-02-14 11:58:12 +00:00
|
|
|
wxT( "*.cir" ), wxT( "*.sub" ), wxT( "*.model" ), // SPICE files
|
|
|
|
wxT( "*.ibs" )
|
2021-02-04 22:24:02 +00:00
|
|
|
};
|
2020-07-03 13:40:31 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
{
|
2021-07-05 12:40:38 +00:00
|
|
|
msg.Printf( _( "Failed to create file '%s'." ), aDestFile );
|
2020-07-03 13:40:31 +00:00
|
|
|
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] );
|
|
|
|
|
2021-02-04 22:24:02 +00:00
|
|
|
if( aIncludeExtraFiles )
|
|
|
|
{
|
|
|
|
for( unsigned ii = 0; ii < arrayDim( extraExtensionList ); ii++ )
|
|
|
|
wxDir::GetAllFiles( aSrcDir, &files, extraExtensionList[ii] );
|
|
|
|
}
|
|
|
|
|
2023-02-14 11:58:12 +00:00
|
|
|
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() );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-03 13:40:31 +00:00
|
|
|
files.Sort();
|
|
|
|
|
|
|
|
unsigned long uncompressedBytes = 0;
|
|
|
|
|
2023-11-21 08:24:14 +00:00
|
|
|
// 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;
|
|
|
|
|
2020-07-03 13:40:31 +00:00
|
|
|
for( unsigned ii = 0; ii < files.GetCount(); ii++ )
|
|
|
|
{
|
2023-11-21 08:24:14 +00:00
|
|
|
if( lastStoredFile == files[ii] ) // duplicate name: already stored
|
|
|
|
continue;
|
|
|
|
|
|
|
|
lastStoredFile = files[ii];
|
|
|
|
|
2020-07-03 13:40:31 +00:00
|
|
|
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 )
|
|
|
|
{
|
2021-07-05 12:40:38 +00:00
|
|
|
msg.Printf( _( "Archived file '%s'." ), currFilename );
|
2020-07-03 13:40:31 +00:00
|
|
|
aReporter.Report( msg, RPT_SEVERITY_INFO );
|
|
|
|
}
|
|
|
|
|
|
|
|
delete infile;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if( aVerbose )
|
|
|
|
{
|
2021-07-05 12:40:38 +00:00
|
|
|
msg.Printf( _( "Failed to archive file '%s'." ), currFilename );
|
2020-07-03 13:40:31 +00:00
|
|
|
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() )
|
|
|
|
{
|
2021-07-05 12:40:38 +00:00
|
|
|
msg.Printf( _( "Zip archive '%s' created (%s uncompressed, %s compressed)." ),
|
2021-06-27 23:26:54 +00:00
|
|
|
aDestFile,
|
|
|
|
reportSize( uncompressedBytes ),
|
|
|
|
reportSize( zipBytesCnt ) );
|
2020-07-03 13:40:31 +00:00
|
|
|
aReporter.Report( msg, RPT_SEVERITY_INFO );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-07-05 12:40:38 +00:00
|
|
|
msg.Printf( wxT( "Failed to create file '%s'." ), aDestFile );
|
2020-07-03 13:40:31 +00:00
|
|
|
aReporter.Report( msg, RPT_SEVERITY_ERROR );
|
|
|
|
success = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
wxSetWorkingDirectory( oldCwd );
|
|
|
|
return success;
|
|
|
|
}
|