mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 02:03:12 +02:00
Fix project archiving.
Use simplified file extension and name checks rather than a regular expression to determine which files to archive. Fix incorrect use of wxFileName which generates invalid paths when the last folder in the path contains dots. Fixes https://gitlab.com/kicad/code/kicad/-/issues/20431
This commit is contained in:
parent
12c82149bf
commit
598850b1f0
@ -21,6 +21,7 @@
|
|||||||
#include <wx/dir.h>
|
#include <wx/dir.h>
|
||||||
#include <wx/filedlg.h>
|
#include <wx/filedlg.h>
|
||||||
#include <wx/fs_zip.h>
|
#include <wx/fs_zip.h>
|
||||||
|
#include <wx/regex.h>
|
||||||
#include <wx/uri.h>
|
#include <wx/uri.h>
|
||||||
#include <wx/wfstream.h>
|
#include <wx/wfstream.h>
|
||||||
#include <wx/zipstrm.h>
|
#include <wx/zipstrm.h>
|
||||||
@ -43,33 +44,13 @@
|
|||||||
class PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER : public wxDirTraverser
|
class PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER : public wxDirTraverser
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER( const std::string& aExtRegex, const wxString& aPrjDir, wxZipOutputStream& aZipFileOutput,
|
PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER( const wxString& aPrjDir ) :
|
||||||
REPORTER& aReporter, bool aVerbose ) :
|
m_prjDir( aPrjDir )
|
||||||
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
|
virtual wxDirTraverseResult OnFile( const wxString& aFilename ) override
|
||||||
{
|
{
|
||||||
if( std::regex_search( aFilename.ToStdString(), m_fileExtRegex ) )
|
m_files.emplace_back( aFilename );
|
||||||
{
|
|
||||||
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;
|
return wxDIR_CONTINUE;
|
||||||
}
|
}
|
||||||
@ -79,76 +60,22 @@ public:
|
|||||||
return wxDIR_CONTINUE;
|
return wxDIR_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned long GetUncompressedBytes() const
|
const std::vector<wxString>& GetFilesToArchive() const
|
||||||
{
|
{
|
||||||
return m_uncompressedBytes;
|
return m_files;
|
||||||
}
|
|
||||||
|
|
||||||
bool GetErrorOccurred() const
|
|
||||||
{
|
|
||||||
return m_errorOccurred;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void addFileToZip( const wxString& aFilename)
|
wxString m_prjDir;
|
||||||
{
|
std::vector<wxString> m_files;
|
||||||
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()
|
PROJECT_ARCHIVER::PROJECT_ARCHIVER()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool PROJECT_ARCHIVER::AreZipArchivesIdentical( const wxString& aZipFileA,
|
bool PROJECT_ARCHIVER::AreZipArchivesIdentical( const wxString& aZipFileA,
|
||||||
const wxString& aZipFileB, REPORTER& aReporter )
|
const wxString& aZipFileB, REPORTER& aReporter )
|
||||||
{
|
{
|
||||||
@ -238,6 +165,7 @@ bool PROJECT_ARCHIVER::Unarchive( const wxString& aSrcFile, const wxString& aDes
|
|||||||
// Now let's set the filetimes based on what's in the zip
|
// Now let's set the filetimes based on what's in the zip
|
||||||
wxFileName outputFileName( fullname );
|
wxFileName outputFileName( fullname );
|
||||||
wxDateTime fileTime = entry->GetDateTime();
|
wxDateTime fileTime = entry->GetDateTime();
|
||||||
|
|
||||||
// For now we set access, mod, create to the same datetime
|
// For now we set access, mod, create to the same datetime
|
||||||
// create (third arg) is only used on Windows
|
// create (third arg) is only used on Windows
|
||||||
outputFileName.SetTimes( &fileTime, &fileTime, &fileTime );
|
outputFileName.SetTimes( &fileTime, &fileTime, &fileTime );
|
||||||
@ -252,74 +180,68 @@ bool PROJECT_ARCHIVER::Archive( const wxString& aSrcDir, const wxString& aDestFi
|
|||||||
REPORTER& aReporter, bool aVerbose, bool aIncludeExtraFiles )
|
REPORTER& aReporter, bool aVerbose, bool aIncludeExtraFiles )
|
||||||
{
|
{
|
||||||
|
|
||||||
#define EXT( ext ) "\\." + ext + "|"
|
std::set<wxString> extensions;
|
||||||
#define NAME( name ) name + "|"
|
std::set<wxString> files; // File names without extensions such as fp-lib-table.
|
||||||
#define EXT_NO_PIPE( ext ) "\\." + ext
|
|
||||||
#define NAME_NO_PIPE( name ) name
|
|
||||||
|
|
||||||
// List of file extensions that are always archived
|
extensions.emplace( FILEEXT::ProjectFileExtension );
|
||||||
std::string fileExtensionRegex = "("
|
extensions.emplace( FILEEXT::ProjectLocalSettingsFileExtension );
|
||||||
EXT( FILEEXT::ProjectFileExtension )
|
extensions.emplace( FILEEXT::KiCadSchematicFileExtension );
|
||||||
EXT( FILEEXT::ProjectLocalSettingsFileExtension )
|
extensions.emplace( FILEEXT::KiCadSymbolLibFileExtension );
|
||||||
EXT( FILEEXT::KiCadSchematicFileExtension )
|
extensions.emplace( FILEEXT::KiCadPcbFileExtension );
|
||||||
EXT( FILEEXT::KiCadSymbolLibFileExtension )
|
extensions.emplace( FILEEXT::KiCadFootprintFileExtension );
|
||||||
EXT( FILEEXT::KiCadPcbFileExtension )
|
extensions.emplace( FILEEXT::DesignRulesFileExtension );
|
||||||
EXT( FILEEXT::KiCadFootprintFileExtension )
|
extensions.emplace( FILEEXT::DrawingSheetFileExtension );
|
||||||
EXT( FILEEXT::DesignRulesFileExtension )
|
extensions.emplace( FILEEXT::KiCadJobSetFileExtension );
|
||||||
EXT( FILEEXT::DrawingSheetFileExtension )
|
extensions.emplace( FILEEXT::JsonFileExtension ); // for design blocks
|
||||||
EXT( FILEEXT::KiCadJobSetFileExtension )
|
extensions.emplace( FILEEXT::WorkbookFileExtension );
|
||||||
EXT( FILEEXT::JsonFileExtension ) // for design blocks
|
|
||||||
EXT( FILEEXT::WorkbookFileExtension ) +
|
files.emplace( FILEEXT::FootprintLibraryTableFileName );
|
||||||
NAME( FILEEXT::FootprintLibraryTableFileName ) +
|
files.emplace( FILEEXT::SymbolLibraryTableFileName );
|
||||||
NAME( FILEEXT::SymbolLibraryTableFileName ) +
|
files.emplace( FILEEXT::DesignBlockLibraryTableFileName );
|
||||||
NAME_NO_PIPE( FILEEXT::DesignBlockLibraryTableFileName );
|
|
||||||
|
|
||||||
// List of additional file extensions that are only archived when aIncludeExtraFiles is true
|
// List of additional file extensions that are only archived when aIncludeExtraFiles is true
|
||||||
if( aIncludeExtraFiles )
|
if( aIncludeExtraFiles )
|
||||||
{
|
{
|
||||||
fileExtensionRegex += "|"
|
extensions.emplace( FILEEXT::LegacyProjectFileExtension );
|
||||||
EXT( FILEEXT::LegacyProjectFileExtension )
|
extensions.emplace( FILEEXT::LegacySchematicFileExtension );
|
||||||
EXT( FILEEXT::LegacySchematicFileExtension )
|
extensions.emplace( FILEEXT::LegacySymbolLibFileExtension );
|
||||||
EXT( FILEEXT::LegacySymbolLibFileExtension )
|
extensions.emplace( FILEEXT::LegacySymbolDocumentFileExtension );
|
||||||
EXT( FILEEXT::LegacySymbolDocumentFileExtension )
|
extensions.emplace( FILEEXT::FootprintAssignmentFileExtension );
|
||||||
EXT( FILEEXT::FootprintAssignmentFileExtension )
|
extensions.emplace( FILEEXT::LegacyPcbFileExtension );
|
||||||
EXT( FILEEXT::LegacyPcbFileExtension )
|
extensions.emplace( FILEEXT::LegacyFootprintLibPathExtension );
|
||||||
EXT( FILEEXT::LegacyFootprintLibPathExtension )
|
extensions.emplace( FILEEXT::StepFileAbrvExtension );
|
||||||
EXT( FILEEXT::StepFileAbrvExtension ) // 3d files
|
extensions.emplace( FILEEXT::StepFileExtension ); // 3d files
|
||||||
EXT( FILEEXT::StepFileExtension ) // 3d files
|
extensions.emplace( FILEEXT::VrmlFileExtension ); // 3d files
|
||||||
EXT( FILEEXT::VrmlFileExtension ) // 3d files
|
extensions.emplace( FILEEXT::GerberJobFileExtension ); // Gerber job files
|
||||||
EXT( FILEEXT::GerberFileExtensionsRegex ) // Gerber files (g?, g??, .gm12 (from protel export))
|
extensions.emplace( FILEEXT::FootprintPlaceFileExtension ); // Our position files
|
||||||
EXT( FILEEXT::GerberJobFileExtension ) // Gerber job files
|
extensions.emplace( FILEEXT::DrillFileExtension ); // Fab drill files
|
||||||
EXT( FILEEXT::FootprintPlaceFileExtension ) // Our position files
|
extensions.emplace( "nc" ); // Fab drill files
|
||||||
EXT( FILEEXT::DrillFileExtension ) // Fab drill files
|
extensions.emplace( "xnc" ); // Fab drill files
|
||||||
EXT( "nc" ) // Fab drill files
|
extensions.emplace( FILEEXT::IpcD356FileExtension );
|
||||||
EXT( "xnc" ) // Fab drill files
|
extensions.emplace( FILEEXT::ReportFileExtension );
|
||||||
EXT( FILEEXT::IpcD356FileExtension )
|
extensions.emplace( FILEEXT::NetlistFileExtension );
|
||||||
EXT( FILEEXT::ReportFileExtension )
|
extensions.emplace( FILEEXT::PythonFileExtension );
|
||||||
EXT( FILEEXT::NetlistFileExtension )
|
extensions.emplace( FILEEXT::PdfFileExtension );
|
||||||
EXT( FILEEXT::PythonFileExtension )
|
extensions.emplace( FILEEXT::TextFileExtension );
|
||||||
EXT( FILEEXT::PdfFileExtension )
|
extensions.emplace( FILEEXT::SpiceFileExtension ); // SPICE files
|
||||||
EXT( FILEEXT::TextFileExtension )
|
extensions.emplace( FILEEXT::SpiceSubcircuitFileExtension ); // SPICE files
|
||||||
EXT( FILEEXT::SpiceFileExtension ) // SPICE files
|
extensions.emplace( FILEEXT::SpiceModelFileExtension ); // SPICE files
|
||||||
EXT( FILEEXT::SpiceSubcircuitFileExtension ) // SPICE files
|
extensions.emplace( FILEEXT::IbisFileExtension );
|
||||||
EXT( FILEEXT::SpiceModelFileExtension ) // SPICE files
|
extensions.emplace( "pkg" );
|
||||||
EXT_NO_PIPE( FILEEXT::IbisFileExtension );
|
extensions.emplace( FILEEXT::GencadFileExtension );
|
||||||
}
|
}
|
||||||
|
|
||||||
fileExtensionRegex += ")";
|
// Gerber files (g?, g??, .gm12 (from protel export)).
|
||||||
|
wxRegEx gerberFiles( FILEEXT::GerberFileExtensionsRegex );
|
||||||
#undef EXT
|
wxASSERT( gerberFiles.IsValid() );
|
||||||
#undef NAME
|
|
||||||
#undef EXT_NO_PIPE
|
|
||||||
#undef NAME_NO_PIPE
|
|
||||||
|
|
||||||
bool success = true;
|
bool success = true;
|
||||||
wxString msg;
|
wxString msg;
|
||||||
wxString oldCwd = wxGetCwd();
|
wxString oldCwd = wxGetCwd();
|
||||||
|
|
||||||
wxFileName sourceDir( aSrcDir );
|
wxFileName sourceDir( aSrcDir, wxEmptyString, wxEmptyString );
|
||||||
|
|
||||||
wxSetWorkingDirectory( sourceDir.GetFullPath() );
|
wxSetWorkingDirectory( aSrcDir );
|
||||||
|
|
||||||
wxFFileOutputStream ostream( aDestFile );
|
wxFFileOutputStream ostream( aDestFile );
|
||||||
|
|
||||||
@ -334,7 +256,6 @@ bool PROJECT_ARCHIVER::Archive( const wxString& aSrcDir, const wxString& aDestFi
|
|||||||
|
|
||||||
wxDir projectDir( aSrcDir );
|
wxDir projectDir( aSrcDir );
|
||||||
wxString currFilename;
|
wxString currFilename;
|
||||||
wxArrayString files;
|
|
||||||
|
|
||||||
if( !projectDir.IsOpened() )
|
if( !projectDir.IsOpened() )
|
||||||
{
|
{
|
||||||
@ -348,58 +269,92 @@ bool PROJECT_ARCHIVER::Archive( const wxString& aSrcDir, const wxString& aDestFi
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
size_t uncompressedBytes = 0;
|
||||||
|
PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER traverser( aSrcDir );
|
||||||
|
|
||||||
|
projectDir.Traverse( traverser );
|
||||||
|
|
||||||
|
for( const wxString& fileName : traverser.GetFilesToArchive() )
|
||||||
{
|
{
|
||||||
PROJECT_ARCHIVER_DIR_ZIP_TRAVERSER traverser( fileExtensionRegex, aSrcDir, zipstream,
|
wxFileName fn( fileName );
|
||||||
aReporter, aVerbose );
|
wxString extLower = fn.GetExt().Lower();
|
||||||
|
wxString fileNameLower = fn.GetName().Lower();
|
||||||
|
bool archive = false;
|
||||||
|
|
||||||
projectDir.Traverse( traverser );
|
if( !extLower.IsEmpty() )
|
||||||
|
|
||||||
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)." ),
|
if( ( extensions.find( extLower ) != extensions.end() )
|
||||||
aDestFile,
|
|| ( aIncludeExtraFiles && gerberFiles.Matches( extLower ) ) )
|
||||||
reportSize( uncompressedBytes ),
|
archive = true;
|
||||||
reportSize( zipBytesCnt ) );
|
}
|
||||||
aReporter.Report( msg, RPT_SEVERITY_INFO );
|
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 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
msg.Printf( wxT( "Failed to create file '%s'." ), aDestFile );
|
if( aVerbose )
|
||||||
aReporter.Report( msg, RPT_SEVERITY_ERROR );
|
{
|
||||||
success = false;
|
msg.Printf( _( "Failed to archive file '%s'." ), relativeFn );
|
||||||
|
aReporter.Report( msg, RPT_SEVERITY_ERROR );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch( const std::regex_error& e )
|
|
||||||
|
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() )
|
||||||
{
|
{
|
||||||
// Something bad happened here with the regex
|
msg.Printf( _( "Zip archive '%s' created (%s uncompressed, %s compressed)." ),
|
||||||
wxASSERT_MSG( false, e.what() );
|
aDestFile,
|
||||||
|
reportSize( uncompressedBytes ),
|
||||||
if( aVerbose )
|
reportSize( zipBytesCnt ) );
|
||||||
{
|
aReporter.Report( msg, RPT_SEVERITY_INFO );
|
||||||
msg.Printf( _( "Error: '%s'." ), e.what() );
|
}
|
||||||
aReporter.Report( msg, RPT_SEVERITY_ERROR );
|
else
|
||||||
}
|
{
|
||||||
|
msg.Printf( wxT( "Failed to create file '%s'." ), aDestFile );
|
||||||
|
aReporter.Report( msg, RPT_SEVERITY_ERROR );
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1349,7 +1349,7 @@ bool SETTINGS_MANAGER::TriggerBackupIfNeeded( REPORTER& aReporter ) const
|
|||||||
return dt;
|
return dt;
|
||||||
};
|
};
|
||||||
|
|
||||||
wxFileName projectPath( Prj().GetProjectPath() );
|
wxFileName projectPath( Prj().GetProjectPath(), wxEmptyString, wxEmptyString );
|
||||||
|
|
||||||
// Skip backup if project path isn't valid or writable
|
// Skip backup if project path isn't valid or writable
|
||||||
if( !projectPath.IsOk() || !projectPath.Exists() || !projectPath.IsDirWritable() )
|
if( !projectPath.IsOk() || !projectPath.Exists() || !projectPath.IsDirWritable() )
|
||||||
|
Loading…
x
Reference in New Issue
Block a user