diff --git a/Themes/_fallback/Languages/en.ini b/Themes/_fallback/Languages/en.ini index 271ac35ee7..11001f5837 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 8c62dda204..db978399d9 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 e5ad98574e..58275a9b31 100644 --- a/src/NotesLoaderSSC.cpp +++ b/src/NotesLoaderSSC.cpp @@ -724,6 +724,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 19fe294c15..021fc87e83 100644 --- a/src/ScreenEdit.cpp +++ b/src/ScreenEdit.cpp @@ -4187,6 +4187,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: @@ -4207,7 +4208,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; SCREENMAN->SystemMessage(AUTOSAVE_SUCCESSFUL); } diff --git a/src/ScreenEditMenu.cpp b/src/ScreenEditMenu.cpp index 282b5825ed..31faaa60ee 100644 --- a/src/ScreenEditMenu.cpp +++ b/src/ScreenEditMenu.cpp @@ -266,6 +266,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 ); { @@ -344,6 +356,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 0e73ab464b..6009d6e0dd 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 ); @@ -1001,12 +1046,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(); @@ -1107,14 +1153,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() ) { @@ -1169,6 +1208,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 b372183ceb..c40149a636 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; @@ -450,6 +454,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. */