diff --git a/stepmania/Themes/default/Languages/en.ini b/stepmania/Themes/default/Languages/en.ini index c23edc2a7e..48bde8d6df 100644 --- a/stepmania/Themes/default/Languages/en.ini +++ b/stepmania/Themes/default/Languages/en.ini @@ -1147,6 +1147,10 @@ These steps will be lost permanently.=These steps will be lost permanently. You have %d step edits, the maximum number allowed.=You have %d step edits, the maximum number allowed. You must delete an existing steps edit before creating a new steps edit.=You must delete an existing steps edit before creating a new steps edit. +[NotesWriterSM] +Error renaming file. Destination file '%s' already exists.=Error renaming file. Destination file '%s' already exists. +Error writing file '%s'.=Error writing file '%s'. + [ScreenOptionsManageProfiles] Are you sure you want to clear all data in the profile '%s'?=Are you sure you want to clear all data in the profile '%s'? Are you sure you want to delete the profile '%s'?=Are you sure you want to delete the profile '%s'? @@ -1348,6 +1352,7 @@ The folder '%s' appears to be a song folder. All song folders must reside in a [SongUtil] You must supply a name for your new edit.=You must supply a name for your new edit. The name you chose conflicts with another edit. Please use a different name.=The name you chose conflicts with another edit. Please use a different name. +The edit name cannot contain any of the following characters: %s=The edit name cannot contain any of the following characters: %s [SongSort] FewestPlays=Fewest Plays diff --git a/stepmania/src/NotesWriterSM.cpp b/stepmania/src/NotesWriterSM.cpp index 30c38ae56d..cc5cd02506 100644 --- a/stepmania/src/NotesWriterSM.cpp +++ b/stepmania/src/NotesWriterSM.cpp @@ -14,6 +14,7 @@ #include "BackgroundUtil.h" #include "ProfileManager.h" #include "Profile.h" +#include "LocalizedString.h" static RString BackgroundChangeToString( const BackgroundChange &bgc ) { @@ -272,7 +273,9 @@ RString NotesWriterSM::GetEditFileName( const Song *pSong, const Steps *pSteps ) return sFile; } -bool NotesWriterSM::WriteEditFileToMachine( const Song *pSong, Steps *pSteps ) +static LocalizedString DESTINATION_ALREADY_EXISTS ("NotesWriterSM", "Error renaming file. Destination file '%s' already exists."); +static LocalizedString ERROR_WRITING_FILE ("NotesWriterSM", "Error writing file '%s'."); +bool NotesWriterSM::WriteEditFileToMachine( const Song *pSong, Steps *pSteps, RString &sErrorOut ) { RString sDir = PROFILEMAN->GetProfileDir( ProfileSlot_Machine ) + EDIT_STEPS_SUBDIR; @@ -281,12 +284,20 @@ bool NotesWriterSM::WriteEditFileToMachine( const Song *pSong, Steps *pSteps ) /* Flush dir cache when writing steps, so the old size isn't cached. */ FILEMAN->FlushDirCache( Dirname(sPath) ); - // TODO: Check to make sure that we're not clobering an existing file before opening. + // Check to make sure that we're not clobering an existing file before opening. + bool bFileNameChanging = + pSteps->GetSavedToDisk() && + pSteps->GetFilename() != sPath; + if( bFileNameChanging && DoesFileExist(sPath) ) + { + sErrorOut = ssprintf( DESTINATION_ALREADY_EXISTS.GetValue(), sPath.c_str() ); + return false; + } RageFile f; if( !f.Open(sPath, RageFile::WRITE | RageFile::SLOW_FLUSH) ) { - LOG->Warn( "Error opening song file '%s' for writing: %s", sPath.c_str(), f.GetError().c_str() ); + sErrorOut = ssprintf( ERROR_WRITING_FILE.GetValue(), sPath.c_str() ); return false; } @@ -294,20 +305,20 @@ bool NotesWriterSM::WriteEditFileToMachine( const Song *pSong, Steps *pSteps ) GetEditFileContents( pSong, pSteps, sTag ); if( f.PutLine(sTag) == -1 || f.Flush() == -1 ) { - LOG->Warn( "Error writing song file '%s': %s", sPath.c_str(), f.GetError().c_str() ); + sErrorOut = ssprintf( ERROR_WRITING_FILE.GetValue(), sPath.c_str() ); return false; } /* If the file name of the edit has changed since the last save, then delete the old * file after saving the new one. If we delete it first, then we'll lose data on error. */ - bool bFileNameChanged = - pSteps->GetSavedToDisk() && - pSteps->GetFilename() != sPath; - if( bFileNameChanged ) + if( bFileNameChanging ) FILEMAN->Remove( pSteps->GetFilename() ); pSteps->SetFilename( sPath ); + /* Flush dir cache or else the new file won't be seen. */ + FILEMAN->FlushDirCache( Dirname(sPath) ); + return true; } diff --git a/stepmania/src/NotesWriterSM.h b/stepmania/src/NotesWriterSM.h index e946bf053c..82a8bc1e7d 100644 --- a/stepmania/src/NotesWriterSM.h +++ b/stepmania/src/NotesWriterSM.h @@ -15,7 +15,7 @@ public: bool Write( RString sPath, const Song &out, bool bSavingCache ); static void GetEditFileContents( const Song *pSong, const Steps *pSteps, RString &sOut ); RString GetEditFileName( const Song *pSong, const Steps *pSteps ); - bool WriteEditFileToMachine( const Song *pSong, Steps *pSteps ); + bool WriteEditFileToMachine( const Song *pSong, Steps *pSteps, RString &sErrorOut ); }; #endif diff --git a/stepmania/src/ScreenEdit.cpp b/stepmania/src/ScreenEdit.cpp index 549e0abcf4..cc64b96dda 100644 --- a/stepmania/src/ScreenEdit.cpp +++ b/stepmania/src/ScreenEdit.cpp @@ -2677,10 +2677,13 @@ void ScreenEdit::HandleMainMenuChoice( MainMenuChoice c, const vector &iAns { ASSERT( pSteps->IsAnEdit() ); - // XXX show error NotesWriterSM w; - if( !w.WriteEditFileToMachine(pSong, pSteps) ) + RString sError; + if( !w.WriteEditFileToMachine(pSong,pSteps,sError) ) + { + ScreenPrompt::Prompt( SM_None, sError ); break; + } // HACK: clear undo, so "exit" below knows we don't need to save. // This only works because important non-steps data can't be changed in diff --git a/stepmania/src/ScreenOptionsManageEditSteps.cpp b/stepmania/src/ScreenOptionsManageEditSteps.cpp index 2c802fb1ce..1e1284d31b 100644 --- a/stepmania/src/ScreenOptionsManageEditSteps.cpp +++ b/stepmania/src/ScreenOptionsManageEditSteps.cpp @@ -14,6 +14,11 @@ #include "LocalizedString.h" #include "OptionRowHandler.h" #include "SongUtil.h" +#include "song.h" +#include "ProfileManager.h" +#include "Profile.h" +#include "SpecialFiles.h" +#include "NotesWriterSM.h" AutoScreenMessage( SM_BackFromRename ) AutoScreenMessage( SM_BackFromDelete ) @@ -49,6 +54,12 @@ void ScreenOptionsManageEditSteps::Init() void ScreenOptionsManageEditSteps::BeginScreen() { + // Reload so that we're consistent with the disk in case the user has been dinking around with their edits. + SONGMAN->FreeAllLoadedFromProfile( ProfileSlot_Machine ); + SONGMAN->LoadAllFromProfileDir( PROFILEMAN->GetProfileDir(ProfileSlot_Machine), ProfileSlot_Machine ); + GAMESTATE->m_pCurSong.Set( NULL ); + GAMESTATE->m_pCurSteps[0].Set( NULL ); + vector vHands; int iIndex = 0; @@ -71,7 +82,10 @@ void ScreenOptionsManageEditSteps::BeginScreen() { vHands.push_back( OptionRowHandlerUtil::MakeNull() ); OptionRowDefinition &def = vHands.back()->m_Def; - def.m_sName = (*s)->GetDescription(); + + Song *pSong = SONGMAN->GetSongFromSteps( *s ); + + def.m_sName = pSong->GetTranslitFullTitle() + " - " + (*s)->GetDescription(); def.m_bAllowThemeTitle = false; // not themable def.m_sExplanationName = "Select Edit Steps"; def.m_vsChoices.clear(); @@ -132,8 +146,20 @@ void ScreenOptionsManageEditSteps::HandleScreenMessage( const ScreenMessage SM ) if( !ScreenTextEntry::s_bCancelledLast ) { ASSERT( ScreenTextEntry::s_sLastAnswer != "" ); // validate should have assured this - - GAMESTATE->m_pCurSteps[PLAYER_1]->SetDescription( ScreenTextEntry::s_sLastAnswer ); + + Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; + Song *pSong = SONGMAN->GetSongFromSteps( pSteps ); + + RString sOldDescription = pSteps->GetDescription(); + pSteps->SetDescription( ScreenTextEntry::s_sLastAnswer ); + + RString sError; + NotesWriterSM sm; + if( !sm.WriteEditFileToMachine(pSong,pSteps,sError) ) + { + ScreenPrompt::Prompt( SM_None, sError ); + return; + } SCREENMAN->SetNewScreen( this->m_sName ); // reload } diff --git a/stepmania/src/SongUtil.cpp b/stepmania/src/SongUtil.cpp index ae359d02da..9a9f4d9666 100644 --- a/stepmania/src/SongUtil.cpp +++ b/stepmania/src/SongUtil.cpp @@ -425,10 +425,11 @@ RString SongUtil::MakeUniqueEditDescription( const Song *pSong, StepsType st, co static LocalizedString YOU_MUST_SUPPLY_NAME ( "SongUtil", "You must supply a name for your new edit." ); static LocalizedString EDIT_NAME_CONFLICTS ( "SongUtil", "The name you chose conflicts with another edit. Please use a different name." ); - +static LocalizedString EDIT_NAME_CANNOT_CONTAIN ( "SongUtil", "The edit name cannot contain any of the following characters: %s" ); bool SongUtil::ValidateCurrentEditStepsDescription( const RString &sAnswer, RString &sErrorOut ) { Steps *pSteps = GAMESTATE->m_pCurSteps[PLAYER_1]; + Song *pSong = SONGMAN->GetSongFromSteps( pSteps ); ASSERT( pSteps->IsAnEdit() ); @@ -438,9 +439,16 @@ bool SongUtil::ValidateCurrentEditStepsDescription( const RString &sAnswer, RStr return false; } - // Steps name must be unique + static const RString sInvalidChars = "\\/:*?\"<>|"; + if( strpbrk(sAnswer, sInvalidChars) != NULL ) + { + sErrorOut = ssprintf( EDIT_NAME_CANNOT_CONTAIN.GetValue(), sInvalidChars.c_str() ); + return false; + } + + // Steps name must be unique for this song. vector v; - SONGMAN->GetStepsLoadedFromProfile( v, ProfileSlot_Machine ); + pSong->GetSteps( v, STEPS_TYPE_INVALID, DIFFICULTY_EDIT ); FOREACH_CONST( Steps*, v, s ) { if( pSteps == *s )