From c9232bce37628518f548abf6a928338d1c3c36bf Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Tue, 17 Feb 2015 15:37:49 -0700 Subject: [PATCH 1/2] Added autosave to edit mode. --- src/NotesLoaderSM.cpp | 11 +-- src/ScreenEdit.cpp | 178 ++++++++++++++++++++++++++---------------- src/ScreenEdit.h | 9 ++- src/Song.cpp | 32 ++++++-- src/Song.h | 7 +- src/Steps.cpp | 3 +- 6 files changed, 148 insertions(+), 92 deletions(-) diff --git a/src/NotesLoaderSM.cpp b/src/NotesLoaderSM.cpp index 11d91d5226..18cdd52310 100644 --- a/src/NotesLoaderSM.cpp +++ b/src/NotesLoaderSM.cpp @@ -27,16 +27,6 @@ bool SMLoader::LoadFromDir( const RString &sPath, Song &out ) { vector aFileNames; GetApplicableFiles( sPath, aFileNames ); - - if( aFileNames.size() > 1 ) - { - // Need to break this up first. - RString tmp = "Song " + sPath + " has more than one"; - LOG->UserLog(tmp, this->GetFileExtension(), "file. There can only be one!"); - return false; - } - - ASSERT( aFileNames.size() == 1 ); return LoadFromSimfile( sPath + aFileNames[0], out ); } @@ -1229,6 +1219,7 @@ bool SMLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath void SMLoader::GetApplicableFiles( const RString &sPath, vector &out ) { + GetDirListing( sPath + RString("*.ats" ), out ); GetDirListing( sPath + RString("*" + this->GetFileExtension() ), out ); } diff --git a/src/ScreenEdit.cpp b/src/ScreenEdit.cpp index 3caf613f97..494d4fe1f8 100644 --- a/src/ScreenEdit.cpp +++ b/src/ScreenEdit.cpp @@ -45,6 +45,7 @@ static Preference g_bEditorShowBGChangesPlay( "EditorShowBGChangesPlay", t /** @brief How long must the button be held to generate a hold in record mode? */ const float record_hold_default= 0.3f; float record_hold_seconds = record_hold_default; +const float time_between_autosave= 300.0f; // 5 minutes. -Kyz #define PLAYER_X (SCREEN_CENTER_X) #define PLAYER_Y (SCREEN_CENTER_Y) @@ -108,6 +109,7 @@ AutoScreenMessage( SM_BackFromFakeChange ); AutoScreenMessage( SM_DoEraseStepTiming ); AutoScreenMessage( SM_DoSaveAndExit ); AutoScreenMessage( SM_DoExit ); +AutoScreenMessage( SM_AutoSaveSuccessful ); AutoScreenMessage( SM_SaveSuccessful ); AutoScreenMessage( SM_SaveFailed ); @@ -1383,7 +1385,11 @@ void ScreenEdit::Init() m_bHasUndo = false; m_Undo.SetNumTracks( m_NoteDataEdit.GetNumTracks() ); - m_bDirty = m_NoteDataEdit.IsEmpty(); // require the usage of saving if empty. + SetDirty(m_NoteDataEdit.IsEmpty()); // require saving if empty. + if(GAMESTATE->m_pCurSong->WasLoadedFromAutosave()) + { + SetDirty(true); + } m_Player->Init( "Player", GAMESTATE->m_pPlayerState[PLAYER_1], NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL ); m_Player->CacheAllUsedNoteSkins(); @@ -1519,6 +1525,16 @@ void ScreenEdit::Update( float fDeltaTime ) GAMESTATE->UpdateSongPosition( fSeconds, GAMESTATE->m_pCurSong->m_SongTiming, tm ); } + if(m_EditState == STATE_EDITING) + { + if(IsDirty() && m_next_autosave_time > -1.0f && + RageTimer::GetTimeSinceStartFast() > m_next_autosave_time) + { + PerformSave(true); + } + } + + if( m_EditState == STATE_RECORDING ) { // TODO: Find a way to prevent STATE_RECORDING when in Song Timing. @@ -1604,7 +1620,6 @@ void ScreenEdit::Update( float fDeltaTime ) } } - //LOG->Trace( "ScreenEdit::Update(%f)", fDeltaTime ); ScreenWithMenuElements::Update( fDeltaTime ); @@ -4166,6 +4181,11 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) else SCREENMAN->SystemMessage( SAVE_SUCCESSFUL ); } + else if( SM == SM_AutoSaveSuccessful ) + { + LOG->Trace( "AutoSave successful." ); + m_next_autosave_time= RageTimer::GetTimeSinceStartFast() + time_between_autosave; + } else if( SM == SM_SaveFailed ) // save failed; stay in the editor { /* We committed the steps to SongManager. Revert to the last save, and @@ -4235,6 +4255,91 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) ScreenWithMenuElements::HandleScreenMessage( SM ); } +void ScreenEdit::SetDirty(bool dirty) +{ + if(dirty) + { + if(!m_dirty) + { + m_next_autosave_time= RageTimer::GetTimeSinceStartFast() + time_between_autosave; + } + } + else + { + m_next_autosave_time= -1.0f; + } + m_dirty= dirty; +} + +void ScreenEdit::PerformSave(bool autosave) +{ + // copy edit into current Steps + m_pSteps->SetNoteData( m_NoteDataEdit ); + + // don't forget the attacks. + m_pSong->m_Attacks = GAMESTATE->m_pCurSong->m_Attacks; + m_pSong->m_sAttackString = GAMESTATE->m_pCurSong->m_Attacks.ToVectorString(); + m_pSteps->m_Attacks = GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks; + m_pSteps->m_sAttackString = GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks.ToVectorString(); + + const ScreenMessage save_message= autosave ? SM_AutoSaveSuccessful + : SM_SaveSuccessful; + + switch( EDIT_MODE.GetValue() ) + { + DEFAULT_FAIL( EDIT_MODE.GetValue() ); + case EditMode_Home: + { + ASSERT( m_pSteps->IsAnEdit() ); + + RString sError; + m_pSteps->CalculateRadarValues( m_pSong->m_fMusicLengthSeconds ); + if( !NotesWriterSM::WriteEditFileToMachine(m_pSong, m_pSteps, sError) ) + { + ScreenPrompt::Prompt( SM_None, sError ); + break; + } + + m_pSteps->SetSavedToDisk( true ); + + // 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 + // home mode (BPMs, stops). + ClearUndo(); + + SCREENMAN->ZeroNextUpdate(); + + HandleScreenMessage(save_message); + + /* FIXME + RString s; + switch( c ) + { + case save: s = "ScreenMemcardSaveEditsAfterSave"; break; + case save_on_exit: s = "ScreenMemcardSaveEditsAfterExit"; break; + default: FAIL_M(ssprintf("Invalid menu choice: %i", c)); + } + SCREENMAN->AddNewScreenToTop( s ); + */ + } + break; + case EditMode_Full: + { + // This will recalculate radar values. + m_pSong->Save(autosave); + SCREENMAN->ZeroNextUpdate(); + + HandleScreenMessage(save_message); + } + break; + case EditMode_CourseMods: + case EditMode_Practice: + break; + } + + m_soundSave.Play(); +} + void ScreenEdit::OnSnapModeChange() { m_soundChangeSnap.Play(); @@ -4622,73 +4727,8 @@ void ScreenEdit::HandleMainMenuChoice( MainMenuChoice c, const vector &iAns } case save: case save_on_exit: - { - m_CurrentAction = c; - - // copy edit into current Steps - m_pSteps->SetNoteData( m_NoteDataEdit ); - - // don't forget the attacks. - m_pSong->m_Attacks = GAMESTATE->m_pCurSong->m_Attacks; - m_pSong->m_sAttackString = GAMESTATE->m_pCurSong->m_Attacks.ToVectorString(); - m_pSteps->m_Attacks = GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks; - m_pSteps->m_sAttackString = GAMESTATE->m_pCurSteps[PLAYER_1]->m_Attacks.ToVectorString(); - - switch( EDIT_MODE.GetValue() ) - { - DEFAULT_FAIL( EDIT_MODE.GetValue() ); - case EditMode_Home: - { - ASSERT( m_pSteps->IsAnEdit() ); - - RString sError; - - m_pSteps->CalculateRadarValues( m_pSong->m_fMusicLengthSeconds ); - if( !NotesWriterSM::WriteEditFileToMachine(m_pSong, m_pSteps, sError) ) - { - ScreenPrompt::Prompt( SM_None, sError ); - break; - } - - m_pSteps->SetSavedToDisk( true ); - - // 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 - // home mode (BPMs, stops). - ClearUndo(); - - SCREENMAN->ZeroNextUpdate(); - - HandleScreenMessage( SM_SaveSuccessful ); - - /* FIXME - RString s; - switch( c ) - { - case save: s = "ScreenMemcardSaveEditsAfterSave"; break; - case save_on_exit: s = "ScreenMemcardSaveEditsAfterExit"; break; - default: FAIL_M(ssprintf("Invalid menu choice: %i", c)); - } - SCREENMAN->AddNewScreenToTop( s ); - */ - } - break; - case EditMode_Full: - { - // This will recalculate radar values. - m_pSong->Save(); - SCREENMAN->ZeroNextUpdate(); - - HandleScreenMessage( SM_SaveSuccessful ); - } - break; - case EditMode_CourseMods: - case EditMode_Practice: - break; - } - - m_soundSave.Play(); - } + m_CurrentAction = c; + PerformSave(false); break; case revert_to_last_save: ScreenPrompt::Prompt( SM_DoRevertToLastSave, REVERT_LAST_SAVE.GetValue() + "\n\n" + DESTROY_ALL_UNSAVED_CHANGES.GetValue(), PROMPT_YES_NO, ANSWER_NO ); diff --git a/src/ScreenEdit.h b/src/ScreenEdit.h index 73958de654..3015f79062 100644 --- a/src/ScreenEdit.h +++ b/src/ScreenEdit.h @@ -217,8 +217,10 @@ public: virtual void HandleMessage( const Message &msg ); virtual void HandleScreenMessage( const ScreenMessage SM ); - void SetDirty( bool bDirty ) { m_bDirty = bDirty; } - bool IsDirty() const { return m_bDirty; } + void SetDirty(bool dirty); + bool IsDirty() const { return m_dirty; } + + void PerformSave(bool autosave); EditState GetEditState(){ return m_EditState; } @@ -326,7 +328,8 @@ protected: NoteData m_Undo; /** @brief Has the NoteData been changed such that a user should be prompted to save? */ - bool m_bDirty; + bool m_dirty; + float m_next_autosave_time; /** @brief The sound that is played when a note is added. */ RageSound m_soundAddNote; diff --git a/src/Song.cpp b/src/Song.cpp index 1ccdf2c8f2..0ed0fcdb20 100644 --- a/src/Song.cpp +++ b/src/Song.cpp @@ -968,7 +968,7 @@ bool Song::HasStepsTypeAndDifficulty( StepsType st, Difficulty dc ) const return SongUtil::GetOneSteps( this, st, dc ) != NULL; } -void Song::Save() +void Song::Save(bool autosave) { LOG->Trace( "Song::SaveToSongFile()" ); @@ -976,11 +976,16 @@ void Song::Save() TranslateTitles(); // Save the new files. These calls make backups on their own. - if( !SaveToSSCFile(GetSongFilePath(), false) ) + if( !SaveToSSCFile(GetSongFilePath(), false, autosave) ) return; + SaveToCacheFile(); + // Skip saving the sm and .old files if we are autosaving. -Kyz + if(autosave) + { + return; + } SaveToSMFile(); //SaveToDWIFile(); - SaveToCacheFile(); /* We've safely written our files and created backups. Rename non-SM and * non-DWI files to avoid confusion. */ @@ -1035,16 +1040,20 @@ bool Song::SaveToSMFile() } -bool Song::SaveToSSCFile( RString sPath, bool bSavingCache ) +bool Song::SaveToSSCFile( RString sPath, bool bSavingCache, bool autosave ) { RString path = sPath; if (!bSavingCache) path = SetExtension(sPath, "ssc"); - + if(autosave) + { + path = SetExtension(sPath, "ats"); + } + LOG->Trace( "Song::SaveToSSCFile('%s')", path.c_str() ); // If the file exists, make a backup. - if( !bSavingCache && IsAFile(path) ) + if(!bSavingCache && !autosave && IsAFile(path)) FileCopy( path, path + ".old" ); vector vpStepsToSave; @@ -1067,7 +1076,7 @@ bool Song::SaveToSSCFile( RString sPath, bool bSavingCache ) vpStepsToSave.push_back(*s); } - if (bSavingCache) + if(bSavingCache || autosave) { return NotesWriterSSC::Write(path, *this, vpStepsToSave, bSavingCache); } @@ -1075,6 +1084,15 @@ bool Song::SaveToSSCFile( RString sPath, bool bSavingCache ) if( !NotesWriterSSC::Write(path, *this, vpStepsToSave, bSavingCache) ) return false; + // Remove the autosave file so that edit mode won't auto load it. -Kyz + RString autosave_path= sPath; + SetExtension(autosave_path, "ats"); + if(FILEMAN->DoesFileExist(autosave_path)) + { + FILEMAN->Remove(autosave_path); + } + + if( g_BackUpAllSongSaves.Get() ) { RString sExt = GetExtension( path ); diff --git a/src/Song.h b/src/Song.h index d786cb1b90..fcfbf834eb 100644 --- a/src/Song.h +++ b/src/Song.h @@ -113,9 +113,9 @@ public: * @param sPath the path where we're saving the file. * @param bSavingCache a flag to determine if we're saving cache data. */ - bool SaveToSSCFile( RString sPath, bool bSavingCache ); + bool SaveToSSCFile(RString sPath, bool bSavingCache, bool autosave= false); /** @brief Save to the SSC and SM files no matter what. */ - void Save(); + void Save(bool autosave= false); /** * @brief Save the current Song to a JSON file. * @return its success or failure. */ @@ -133,6 +133,9 @@ public: * @return its success or failure. */ bool SaveToDWIFile(); + bool WasLoadedFromAutosave() const + { return GetExtension(m_sSongFileName) == "ats"; } + const RString &GetSongFilePath() const; RString GetCacheFilePath() const; diff --git a/src/Steps.cpp b/src/Steps.cpp index 0dbc71c05c..5a8f8d6d94 100644 --- a/src/Steps.cpp +++ b/src/Steps.cpp @@ -105,7 +105,8 @@ bool Steps::GetNoteDataFromSimfile() RString extension = GetExtension(stepFile); extension.MakeLower(); // must do this because the code is expecting lowercase - if (extension.empty() || extension == "ssc") // remember cache files. + if (extension.empty() || extension == "ssc" + || extension == "ats") // remember cache files. { SSCLoader loader; if ( ! loader.LoadNoteDataFromSimfile(stepFile, *this) ) From e03efc8c5c53236d0646c33e964960c24632baa7 Mon Sep 17 00:00:00 2001 From: Kyzentun Date: Fri, 27 Feb 2015 10:36:17 -0700 Subject: [PATCH 2/2] Autosave completed but not tested. --- Themes/_fallback/Languages/en.ini | 1 + src/EditMenu.cpp | 7 +++ src/EditMenu.h | 1 + src/NotesLoader.cpp | 6 +- src/NotesLoader.h | 2 +- src/NotesLoaderSM.cpp | 16 ++++-- src/NotesLoaderSM.h | 4 +- src/NotesLoaderSSC.cpp | 4 ++ src/ScreenEdit.cpp | 3 +- src/ScreenEditMenu.cpp | 13 +++++ src/Song.cpp | 91 +++++++++++++++++++++++++------ src/Song.h | 9 ++- 12 files changed, 127 insertions(+), 30 deletions(-) diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index 27ff6dec1e..ba7ef3e480 100644 --- a/Themes/_fallback/Languages/en.ini +++ b/Themes/_fallback/Languages/en.ini @@ -97,6 +97,7 @@ Create=Create New Delete=Delete Existing Edit=Edit Existing Practice=Practice +LoadAutosave=Load Autosave [EditMenu] Blank=Blank diff --git a/src/EditMenu.cpp b/src/EditMenu.cpp index 69c4641b14..6a42a113b8 100644 --- a/src/EditMenu.cpp +++ b/src/EditMenu.cpp @@ -31,6 +31,7 @@ static const char *EditMenuActionNames[] = { "Delete", "Create", "Practice", + "LoadAutosave", }; XToString( EditMenuAction ); XToLocalizedString( EditMenuAction ); @@ -659,6 +660,12 @@ void EditMenu::OnRowValueChanged( EditMenuRow row ) m_StepsDisplaySource.SetVisible( !(bHideMeter || GetSelectedSteps()) ); m_Actions.clear(); + // Stick autosave in the list first so that people will see it. -Kyz + Song* cur_song= GetSelectedSong(); + if(cur_song != NULL && cur_song->HasAutosaveFile() && !cur_song->WasLoadedFromAutosave()) + { + m_Actions.push_back(EditMenuAction_LoadAutosave); + } if( GetSelectedSteps() ) { switch( mode ) diff --git a/src/EditMenu.h b/src/EditMenu.h index a468e34b0c..1d7b912ed3 100644 --- a/src/EditMenu.h +++ b/src/EditMenu.h @@ -42,6 +42,7 @@ enum EditMenuAction EditMenuAction_Delete, /**< Remove the current step from the Song. */ EditMenuAction_Create, /**< Create a new step for the Song. */ EditMenuAction_Practice, /**< Practice the current step for the Song. */ + EditMenuAction_LoadAutosave, NUM_EditMenuAction, /**< The number of MenuActions available to choose from. */ EditMenuAction_Invalid }; diff --git a/src/NotesLoader.cpp b/src/NotesLoader.cpp index 9958490b7c..2b655e1caf 100644 --- a/src/NotesLoader.cpp +++ b/src/NotesLoader.cpp @@ -25,16 +25,16 @@ void NotesLoader::GetMainAndSubTitlesFromFullTitle( const RString &sFullTitle, R sSubTitleOut = ""; }; -bool NotesLoader::LoadFromDir( const RString &sPath, Song &out, set &BlacklistedImages ) +bool NotesLoader::LoadFromDir( const RString &sPath, Song &out, set &BlacklistedImages, bool load_autosave ) { vector list; BlacklistedImages.clear(); SSCLoader loaderSSC; - loaderSSC.GetApplicableFiles( sPath, list ); + loaderSSC.GetApplicableFiles( sPath, list, load_autosave ); if( !list.empty() ) { - if( !loaderSSC.LoadFromDir( sPath, out ) ) + if( !loaderSSC.LoadFromDir( sPath, out, load_autosave ) ) return false; loaderSSC.TidyUpData( out, false ); return true; diff --git a/src/NotesLoader.h b/src/NotesLoader.h index 3be4454961..b9047bc41c 100644 --- a/src/NotesLoader.h +++ b/src/NotesLoader.h @@ -21,7 +21,7 @@ namespace NotesLoader * @param out the Song in question. * @param BlacklistedImages images to exclude (DWI files only for some reason). * @return its success or failure. */ - bool LoadFromDir( const RString &sPath, Song &out, set &BlacklistedImages ); + bool LoadFromDir( const RString &sPath, Song &out, set &BlacklistedImages, bool load_autosave= false ); } #endif diff --git a/src/NotesLoaderSM.cpp b/src/NotesLoaderSM.cpp index 18cdd52310..aeada61e7b 100644 --- a/src/NotesLoaderSM.cpp +++ b/src/NotesLoaderSM.cpp @@ -23,10 +23,10 @@ RString SMLoader::GetSongTitle() const return this->songTitle; } -bool SMLoader::LoadFromDir( const RString &sPath, Song &out ) +bool SMLoader::LoadFromDir( const RString &sPath, Song &out, bool load_autosave ) { vector aFileNames; - GetApplicableFiles( sPath, aFileNames ); + GetApplicableFiles( sPath, aFileNames, load_autosave ); return LoadFromSimfile( sPath + aFileNames[0], out ); } @@ -1217,10 +1217,16 @@ bool SMLoader::LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath return false; } -void SMLoader::GetApplicableFiles( const RString &sPath, vector &out ) +void SMLoader::GetApplicableFiles( const RString &sPath, vector &out, bool load_autosave ) { - GetDirListing( sPath + RString("*.ats" ), out ); - GetDirListing( sPath + RString("*" + this->GetFileExtension() ), out ); + if(load_autosave) + { + GetDirListing( sPath + RString("*.ats" ), out ); + } + else + { + GetDirListing( sPath + RString("*" + this->GetFileExtension() ), out ); + } } void SMLoader::TidyUpData( Song &song, bool bFromCache ) diff --git a/src/NotesLoaderSM.h b/src/NotesLoaderSM.h index 358515ec51..70385574ca 100644 --- a/src/NotesLoaderSM.h +++ b/src/NotesLoaderSM.h @@ -34,7 +34,7 @@ struct SMLoader * @param out a reference to the Song that will retrieve the song information. * @return its success or failure. */ - virtual bool LoadFromDir( const RString &sPath, Song &out ); + virtual bool LoadFromDir( const RString &sPath, Song &out, bool load_autosave= false ); /** * @brief Perform some cleanup on the loaded song. * @param song a reference to the song that may need cleaning up. @@ -61,7 +61,7 @@ struct SMLoader * @param sPath a const reference to the path on the hard drive to check. * @param out a vector of files found in the path. */ - virtual void GetApplicableFiles( const RString &sPath, vector &out ); + virtual void GetApplicableFiles( const RString &sPath, vector &out, bool load_autosave= false ); virtual bool LoadEditFromFile( RString sEditFilePath, ProfileSlot slot, bool bAddStepsToSong, Song *givenSong=NULL ); virtual bool LoadEditFromBuffer( const RString &sBuffer, const RString &sEditFilePath, ProfileSlot slot, Song *givenSong=NULL ); virtual bool LoadEditFromMsd( const MsdFile &msd, const RString &sEditFilePath, ProfileSlot slot, bool bAddStepsToSong, Song *givenSong=NULL ); diff --git a/src/NotesLoaderSSC.cpp b/src/NotesLoaderSSC.cpp index 5f1d084dfb..9b9fdbfab7 100644 --- a/src/NotesLoaderSSC.cpp +++ b/src/NotesLoaderSSC.cpp @@ -719,6 +719,10 @@ bool SSCLoader::LoadFromSimfile( const RString &sPath, Song &out, bool bFromCach bHasOwnTiming = false; pNewNotes->SetSMNoteData( sParams[1] ); pNewNotes->TidyUpData(); + // Unlike the song and timing filenames, the steps filenames do + // need to point to the autosave file if that's where they were + // loaded from, because that's where the actual steps data is. + // -Kyz pNewNotes->SetFilename(sPath); out.AddSteps( pNewNotes ); } diff --git a/src/ScreenEdit.cpp b/src/ScreenEdit.cpp index 494d4fe1f8..3cd070f8d4 100644 --- a/src/ScreenEdit.cpp +++ b/src/ScreenEdit.cpp @@ -4163,6 +4163,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) return; case ANSWER_NO: // Don't save; just exit. + m_pSong->RemoveAutosave(); SCREENMAN->SendMessageToTopScreen( SM_DoExit ); return; case ANSWER_CANCEL: @@ -4183,7 +4184,7 @@ void ScreenEdit::HandleScreenMessage( const ScreenMessage SM ) } else if( SM == SM_AutoSaveSuccessful ) { - LOG->Trace( "AutoSave successful." ); + LOG->Trace("AutoSave successful."); m_next_autosave_time= RageTimer::GetTimeSinceStartFast() + time_between_autosave; } else if( SM == SM_SaveFailed ) // save failed; stay in the editor diff --git a/src/ScreenEditMenu.cpp b/src/ScreenEditMenu.cpp index dadac121bd..7f9032f6c3 100644 --- a/src/ScreenEditMenu.cpp +++ b/src/ScreenEditMenu.cpp @@ -262,6 +262,18 @@ bool ScreenEditMenu::MenuStart( const InputEventPlus & ) ScreenPrompt::Prompt( SM_None, STEPS_WILL_BE_LOST.GetValue() + "\n\n" + CONTINUE_WITH_DELETE.GetValue(), PROMPT_YES_NO, ANSWER_NO ); break; + case EditMenuAction_LoadAutosave: + if(pSong) + { + FOREACH_PlayerNumber(pn) + { + GAMESTATE->m_pCurSteps[pn].Set(NULL); + } + pSong->LoadAutosaveFile(); + SONGMAN->Invalidate(pSong); + SCREENMAN->SendMessageToTopScreen( SM_RefreshSelector ); + } + break; case EditMenuAction_Create: ASSERT( !pSteps ); { @@ -340,6 +352,7 @@ bool ScreenEditMenu::MenuStart( const InputEventPlus & ) } return true; case EditMenuAction_Delete: + case EditMenuAction_LoadAutosave: return true; default: FAIL_M(ssprintf("Invalid edit menu action: %i", action)); diff --git a/src/Song.cpp b/src/Song.cpp index 0ed0fcdb20..229116f663 100644 --- a/src/Song.cpp +++ b/src/Song.cpp @@ -83,6 +83,7 @@ Song::Song() m_bHasMusic = false; m_bHasBanner = false; m_bHasBackground = false; + m_loaded_from_autosave= false; } Song::~Song() @@ -266,7 +267,7 @@ static set BlacklistedImages; * HasMusic(), HasBanner() or GetHashForDirectory(). * If true, check the directory hash and reload the song from scratch if it's changed. */ -bool Song::LoadFromSongDir( RString sDir ) +bool Song::LoadFromSongDir( RString sDir, bool load_autosave ) { // LOG->Trace( "Song::LoadFromSongDir(%s)", sDir.c_str() ); ASSERT_M( sDir != "", "Songs can't be loaded from an empty directory!" ); @@ -293,7 +294,9 @@ bool Song::LoadFromSongDir( RString sDir ) if( !DoesFileExist(sCacheFilePath) ) bUseCache = false; if( !PREFSMAN->m_bFastLoad && GetHashForDirectory(m_sSongDir) != uCacheHash ) - bUseCache = false; // this cache is out of date + bUseCache = false; // this cache is out of date + if(load_autosave) + { bUseCache= false; } if( bUseCache ) { @@ -317,7 +320,7 @@ bool Song::LoadFromSongDir( RString sDir ) // There was no entry in the cache for this song, or it was out of date. // Let's load it from a file, then write a cache entry. - if( !NotesLoader::LoadFromDir(sDir, *this, BlacklistedImages) ) + if(!NotesLoader::LoadFromDir(sDir, *this, BlacklistedImages, load_autosave)) { LOG->UserLog( "Song", sDir, "has no SSC, SM, SMA, DWI, BMS, or KSF files." ); @@ -341,9 +344,14 @@ bool Song::LoadFromSongDir( RString sDir ) } TidyUpData(false, true); - // save a cache file so we don't have to parse it all over again next time - if( !SaveToCacheFile() ) - sCacheFilePath = RString(); + // Don't save a cache file if the autosave is being loaded, because the + // cache file would contain the autosave filename. -Kyz + if(!load_autosave) + { + // save a cache file so we don't have to parse it all over again next time + if(!SaveToCacheFile()) + { sCacheFilePath = RString(); } + } } FOREACH( Steps*, m_vpSteps, s ) @@ -461,6 +469,43 @@ bool Song::ReloadFromSongDir( RString sDir ) return true; } +bool Song::HasAutosaveFile() +{ + if(m_sSongFileName.empty()) + { + return false; + } + RString autosave_path= SetExtension(m_sSongFileName, "ats"); + return FILEMAN->DoesFileExist(autosave_path); +} + +bool Song::LoadAutosaveFile() +{ + if(m_sSongFileName.empty()) + { + return false; + } + // Save these strings because they need to be restored after the reset. + // The filenames need to point to the original instead of the autosave for + // things like load from disk to work. -Kyz + RString dir= GetSongDir(); + RString song_timing_file= m_SongTiming.m_sFile; + RString song_file= m_sSongFileName; + // Reset needs to be used to remove all the steps and other things that + // will be loaded from the autosave. -Kyz + Reset(); + if(LoadFromSongDir(dir, true)) + { + m_loaded_from_autosave= true; + m_sSongFileName= song_file; + m_SongTiming.m_sFile= song_timing_file; + return true; + } + // Loading the autosave failed, reload the original. -Kyz + LoadFromSongDir(dir, false); + return false; +} + static void GetImageDirListing( RString sPath, vector &AddTo ) { GetDirListing( sPath + ".png", AddTo, false, false ); @@ -978,12 +1023,13 @@ void Song::Save(bool autosave) // Save the new files. These calls make backups on their own. if( !SaveToSSCFile(GetSongFilePath(), false, autosave) ) return; - SaveToCacheFile(); - // Skip saving the sm and .old files if we are autosaving. -Kyz + // Skip saving the cache, sm, and .old files if we are autosaving. The + // cache file should not contain the autosave filename. -Kyz if(autosave) { return; } + SaveToCacheFile(); SaveToSMFile(); //SaveToDWIFile(); @@ -1084,14 +1130,7 @@ bool Song::SaveToSSCFile( RString sPath, bool bSavingCache, bool autosave ) if( !NotesWriterSSC::Write(path, *this, vpStepsToSave, bSavingCache) ) return false; - // Remove the autosave file so that edit mode won't auto load it. -Kyz - RString autosave_path= sPath; - SetExtension(autosave_path, "ats"); - if(FILEMAN->DoesFileExist(autosave_path)) - { - FILEMAN->Remove(autosave_path); - } - + RemoveAutosave(); if( g_BackUpAllSongSaves.Get() ) { @@ -1146,6 +1185,26 @@ bool Song::SaveToDWIFile() return NotesWriterDWI::Write( sPath, *this ); } +void Song::RemoveAutosave() +{ + RString autosave_path= SetExtension(m_sSongFileName, "ats"); + if(FILEMAN->DoesFileExist(autosave_path)) + { + // Change all the steps to point to the actual file, not the autosave + // file. -Kyz + RString extension= GetExtension(m_sSongFileName); + for(size_t i= 0; i < m_vpSteps.size(); ++i) + { + if(!m_vpSteps[i]->IsAutogen()) + { + m_vpSteps[i]->SetFilename( + SetExtension(m_vpSteps[i]->GetFilename(), extension)); + } + } + FILEMAN->Remove(autosave_path); + m_loaded_from_autosave= false; + } +} void Song::AddAutoGenNotes() { diff --git a/src/Song.h b/src/Song.h index fcfbf834eb..bafa5376c3 100644 --- a/src/Song.h +++ b/src/Song.h @@ -87,10 +87,13 @@ public: * * This assumes that there is no song present right now. * @param sDir the song directory from which to load. */ - bool LoadFromSongDir( RString sDir ); + bool LoadFromSongDir( RString sDir, bool load_autosave= false ); // This one takes the effort to reuse Steps pointers as best as it can bool ReloadFromSongDir( RString sDir ); + bool HasAutosaveFile(); + bool LoadAutosaveFile(); + /** * @brief Call this after loading a song to clean up invalid data. * @param fromCache was this data loaded from the cache file? @@ -133,8 +136,9 @@ public: * @return its success or failure. */ bool SaveToDWIFile(); + void RemoveAutosave(); bool WasLoadedFromAutosave() const - { return GetExtension(m_sSongFileName) == "ats"; } + { return m_loaded_from_autosave; } const RString &GetSongFilePath() const; RString GetCacheFilePath() const; @@ -446,6 +450,7 @@ public: void PushSelf( lua_State *L ); private: + bool m_loaded_from_autosave; /** @brief the Steps that belong to this Song. */ vector m_vpSteps; /** @brief the Steps of a particular StepsType that belong to this Song. */