20#ifndef DOWNGRADE_SCAN_H
21#define DOWNGRADE_SCAN_H
34#include <wx/filename.h>
51 bool inString =
false;
53 for(
size_t i = 0; i < aText.size(); i++ )
61 size_t backslashes = 0;
63 for(
size_t k = i; k > 0 && aText[k - 1] ==
'\\'; k-- )
66 if( backslashes % 2 == 0 )
83 const std::string needle =
"(" + aToken;
85 for(
size_t pos = aText.find( needle ); pos != std::string::npos; pos = aText.find( needle, pos + 1 ) )
88 size_t after = pos + needle.size();
89 char next = ( after < aText.size() ) ? aText[after] :
' ';
91 if( !std::isalnum(
static_cast<unsigned char>(
next ) ) &&
next !=
'_' )
103 for(
size_t pos = aText.find( aToken ); pos != std::string::npos; pos = aText.find( aToken, pos + 1 ) )
105 char prev = pos > 0 ? aText[pos - 1] :
' ';
106 size_t after = pos + aToken.size();
107 char next = after < aText.size() ? aText[after] :
' ';
109 bool prevOk = !std::isalnum(
static_cast<unsigned char>( prev ) ) && prev !=
'_';
110 bool nextOk = !std::isalnum(
static_cast<unsigned char>(
next ) ) &&
next !=
'_';
112 if( prevOk && nextOk )
123 const DATED_TOKEN* aValues,
size_t aValueCount,
int aTargetVersion )
129 for(
size_t i = 0; i < aNodeCount; i++ )
132 return wxString::FromUTF8( aNodes[i].m_token );
135 for(
size_t i = 0; i < aValueCount; i++ )
138 return wxString::FromUTF8( aValues[i].m_token );
141 return wxEmptyString;
148 std::set<std::string> heads;
150 for(
size_t i = 0; i + 1 < aText.size(); i++ )
152 if( aText[i] !=
'(' || !std::islower(
static_cast<unsigned char>( aText[i + 1] ) ) )
157 while( j < aText.size()
158 && ( std::islower(
static_cast<unsigned char>( aText[j] ) )
159 || std::isdigit(
static_cast<unsigned char>( aText[j] ) ) || aText[j] ==
'_' ) )
164 heads.insert( aText.substr( i + 1, j - i - 1 ) );
175 wxFileName fn( aPath );
177 if( fn.GetFullName().StartsWith( wxT(
"_autosave-" ) ) )
180 for(
const wxString& dir : fn.GetDirs() )
182 if( dir == wxT(
".history" ) || dir == wxT(
".git" ) || dir.EndsWith( wxT(
"-backups" ) ) )
195 wxFileName fn( aPath );
196 wxString ext = fn.GetExt();
198 if( ext == wxT(
"kicad_pro" ) || ext == wxT(
"kicad_sch" ) || ext == wxT(
"kicad_pcb" ) || ext == wxT(
"kicad_dru" )
199 || ext == wxT(
"kicad_wks" ) || ext == wxT(
"kicad_sym" ) || ext == wxT(
"kicad_mod" ) )
204 wxString
name = fn.GetFullName();
205 return name == wxT(
"sym-lib-table" ) ||
name == wxT(
"fp-lib-table" );
212 const std::function<wxString(
const wxString& )>& aFindForbidden,
213 wxString* aForbidden =
nullptr )
219 wxFFile file( aPath, wxT(
"rb" ) );
220 readOk = file.IsOpened() && file.ReadAll( &content );
226 if( !content.Contains( wxString::Format( wxT(
"(version %d)" ), aStampVersion ) ) )
229 wxString forbidden = aFindForbidden( content );
232 *aForbidden = forbidden;
234 return forbidden.IsEmpty();
261 if( !entry.m_keepBackup )
267 wxString
Stage(
const wxString& aDestination )
269 wxFileName destination( aDestination );
271 if(
m_finished || destination.GetFullName().IsEmpty() || !destination.MakeAbsolute() || !destination.DirExists()
272 || wxDirExists( destination.GetFullPath() ) )
274 return wxEmptyString;
279 if( wxFileName( entry.m_destination ).SameAs( destination ) )
280 return wxEmptyString;
283 wxString temporary = wxFileName::CreateTempFileName( destination.GetFullPath() + wxT(
".downgrade_tmp." ) );
285 if( temporary.IsEmpty() )
286 return wxEmptyString;
291 m_entries.push_back( std::move( entry ) );
300 if( it->m_temp == aTemporary )
321 if( !wxFileExists( entry.m_temp ) || wxDirExists( entry.m_destination ) )
322 return entry.m_destination;
324 std::error_code error;
325 auto destination =
nativePath( entry.m_destination );
326 bool symlink = std::filesystem::is_symlink( destination, error );
328 if( error && error != std::errc::no_such_file_or_directory )
329 return entry.m_destination;
331 entry.m_hadOriginal = symlink || wxFileExists( entry.m_destination );
333 if( !entry.m_hadOriginal )
336 if( wxFileExists( entry.m_destination ) )
338 auto permissions = std::filesystem::status( destination, error ).permissions();
341 return entry.m_destination;
343 std::filesystem::permissions(
nativePath( entry.m_temp ), permissions, error );
346 return entry.m_destination;
349 entry.m_backup = wxFileName::CreateTempFileName( entry.m_destination + wxT(
".downgrade_backup." ) );
351 if( entry.m_backup.IsEmpty() )
352 return entry.m_destination;
357 if( !wxRemoveFile( entry.m_backup ) )
358 return entry.m_destination;
360 wxString backup = entry.m_backup;
361 entry.m_backup.clear();
362 std::filesystem::copy_symlink( destination,
nativePath( backup ), error );
365 return entry.m_destination;
367 entry.m_backup = backup;
369 else if( !wxCopyFile( entry.m_destination, entry.m_backup,
true ) )
371 return entry.m_destination;
378 entry.m_attemptedReplacement =
true;
380 if( !wxRenameFile( entry.m_temp, entry.m_destination,
true ) )
383 return entry.m_destination;
386 entry.m_temp.clear();
389 return wxEmptyString;
409 wxRemoveFile( aPath );
414 if( aPath.IsEmpty() )
417 std::error_code error;
418 return wxFileExists( aPath ) || std::filesystem::is_symlink(
nativePath( aPath ), error );
421 static std::filesystem::path
nativePath(
const wxString& aPath )
424 return std::filesystem::path( aPath.ToStdWstring() );
426 return std::filesystem::path( aPath.utf8_string() );
448 m_recoveryError += wxString::Format(
_(
"Could not restore '%s'. The original is saved at '%s'." ),
456 wxString::Format(
_(
"Could not remove incomplete output '%s'." ), entry.
m_destination )
474 const std::function<wxString(
const wxString& )>& aFindForbidden,
479 aRecoveryError->clear();
482 wxDir::GetAllFiles( aDir, &files, aGlob );
487 for(
const wxString& file : files )
492 wxString temporary = transaction.
Stage( file );
494 if( temporary.IsEmpty() )
497 switch( aConvert( file, temporary ) )
511 return wxEmptyString;
513 wxString failed = transaction.
Commit();
Stage related design and library files together, retaining originals until every write is ready.
static std::filesystem::path nativePath(const wxString &aPath)
static void removeOwnedFile(const wxString &aPath)
const wxString & GetRecoveryError() const
Recovery details are nonempty only if restoring an original or removing a new output failed.
std::vector< ENTRY > m_entries
~DOWNGRADE_FILE_TRANSACTION()
DOWNGRADE_FILE_TRANSACTION(const DOWNGRADE_FILE_TRANSACTION &)=delete
DOWNGRADE_FILE_TRANSACTION()=default
wxString Commit()
Replace all destinations, rolling back earlier replacements if a rename fails.
wxString Stage(const wxString &aDestination)
Return an exclusively-created sibling staging file, or empty if it cannot be created.
DOWNGRADE_FILE_TRANSACTION & operator=(const DOWNGRADE_FILE_TRANSACTION &)=delete
static bool ownedFileExists(const wxString &aPath)
void Discard(const wxString &aTemporary)
Forget a skipped conversion without exposing its empty staging file as an output.
wxString DowngradeLibraryFilesInPlace(const wxString &aDir, const wxString &aGlob, int aStampVersion, const std::function< wxString(const wxString &)> &aFindForbidden, const std::function< DOWNGRADE_FILE_RESULT(const wxString &, const wxString &)> &aConvert, DOWNGRADE_FILE_TRANSACTION *aTransaction=nullptr, wxString *aRecoveryError=nullptr)
Downgrade every library file matching aGlob under aDir in place.
bool IsDowngradeWorkingArtifact(const wxString &aPath)
Editor working state (autosaves, local history, backups) holds current-format copies nothing downgrad...
bool IsDowngradeDesignFile(const wxString &aPath)
Files the project export carries over.
wxString FindUnsupportedToken(const wxString &aSerialized, const DATED_TOKEN *aNodes, size_t aNodeCount, const DATED_TOKEN *aValues, size_t aValueCount, int aTargetVersion)
Scan serialized s-expression text against dated node and value denylists.
std::string StripSexprStrings(const std::string &aText)
Drop quoted strings so a token inside a text value cannot cause a false refusal.
bool ContainsSexprValue(const std::string &aText, const std::string &aToken)
Some features are written as a bareword value, not a node head, so the node scan cannot see them.
std::set< std::string > ExtractSexprNodeHeads(const std::string &aText)
Every lowercase node head in the text, e.g. for checking output against a release keyword set.
bool ContainsSexprNode(const std::string &aText, const std::string &aToken)
DOWNGRADE_FILE_RESULT
What a per-file converter did with one library file.
@ SKIPPED
Not a convertible file, leave it alone.
@ REFUSED
The target cannot represent this file.
@ CONVERTED
The temp file holds the downgraded copy.
bool VerifyDowngradedFile(const wxString &aPath, int aStampVersion, const std::function< wxString(const wxString &)> &aFindForbidden, wxString *aForbidden=nullptr)
Read back a downgraded output and require the target stamp plus a clean token scan.
A token dated by the format that introduced it, for the fail-closed downgrade gates.
bool m_attemptedReplacement
wxString result
Test unit parsing edge cases and error handling.