7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2024-11-24 00:34:47 +00:00
kicad/common/project/project_archiver.cpp
Harry Best cc5fb60d93 CHANGED: Backup project only when different
Makes a test backup file and compares to the existing zip file backup to
see if the files have changed since it was made.  If so, the new file is
kept.  If not, we discard the new file and continue

Fixes https://gitlab.com/kicad/code/kicad/-/issues/12453
2024-07-11 10:48:13 -07:00

318 lines
10 KiB
C++

/*
* 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/>.
*/
#include <memory>
#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>
#include <core/arraydim.h>
#include <macros.h>
#include <project/project_archiver.h>
#include <reporter.h>
#include <wildcards_and_files_ext.h>
#include <wxstream_helper.h>
#include <wx/log.h>
#include <set>
#define ZipFileExtension wxT( "zip" )
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<wxUint32> crcsA;
std::set<wxUint32> 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<wxArchiveInputStream> 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;
}