diff --git a/src/NotesLoaderSSC.cpp b/src/NotesLoaderSSC.cpp index 7655ee24e7..8a12438a49 100644 --- a/src/NotesLoaderSSC.cpp +++ b/src/NotesLoaderSSC.cpp @@ -460,7 +460,7 @@ void SetChartKey(SSC::StepsTagInfo& info) { info.steps->SetChartKey((*info.params)[1]); } -vector msdsplit(const RString& s) { +vector SSC::msdsplit(const RString& s) { vector o; for (size_t i = 0; i < s.size(); ++i) { o.emplace_back(StringToFloat(s.substr(i, 5))); @@ -477,7 +477,7 @@ void SetMSDValues(SSC::StepsTagInfo& info) { auto size = params.params.size(); // Start from index 1 for (size_t i = 1; i <= size; i++) - o.emplace_back(msdsplit(params[i])); + o.emplace_back(SSC::msdsplit(params[i])); info.steps->SetAllMSD(o); } diff --git a/src/NotesLoaderSSC.h b/src/NotesLoaderSSC.h index 6923962e77..9b0e3bc423 100644 --- a/src/NotesLoaderSSC.h +++ b/src/NotesLoaderSSC.h @@ -9,7 +9,7 @@ class MsdFile; class Song; class Steps; class TimingData; - +class SSCLoader; /** * @brief The various states while parsing a .ssc file. */ diff --git a/src/NotesWriterSSC.cpp b/src/NotesWriterSSC.cpp index 5a0b764a6f..90a1577742 100644 --- a/src/NotesWriterSSC.cpp +++ b/src/NotesWriterSSC.cpp @@ -33,20 +33,25 @@ static RString JoinLineList( vector &lines ) return join( "\r\n", lines.begin()+j, lines.end() ); } -RString MSDToString(MinaSD x) { +RString NotesWriterSSC::MSDToString(MinaSD x) { RString o = ""; for (size_t i = 0; i < x.size(); i++) { - for (size_t ii = 0; ii < x[i].size(); ii++) { - o.append(to_string(x[i][ii]).substr(0,5)); - if (ii != x[i].size() - 1) - o.append(","); - } + o.append(NotesWriterSSC::SkillsetDiffsToString(x[i])); if (i != x.size() - 1) o.append(":"); } return o; } +RString NotesWriterSSC::SkillsetDiffsToString(SDiffs x) { + RString o = ""; + for (size_t ii = 0; ii < x.size(); ii++) { + o.append(to_string(x[ii]).substr(0, 5)); + if (ii != x.size() - 1) + o.append(","); + } + return o; +} // A utility class to write timing tags more easily! struct TimingTagWriter { @@ -361,7 +366,7 @@ static RString GetSSCNoteData( const Song &song, const Steps &in, bool bSavingCa emplace_back_tag(lines, "#CHARTSTYLE:%s;", in.GetChartStyle()); emplace_back_tag(lines, "#DIFFICULTY:%s;", DifficultyToString(in.GetDifficulty())); lines.emplace_back(ssprintf("#METER:%d;", in.GetMeter())); - lines.emplace_back(ssprintf("#MSDVALUES:%s;", MSDToString(in.GetAllMSD()).c_str())); + lines.emplace_back(ssprintf("#MSDVALUES:%s;", NotesWriterSSC::MSDToString(in.GetAllMSD()).c_str())); lines.emplace_back(ssprintf("#CHARTKEY:%s;", SmEscape(in.GetChartKey()).c_str())); emplace_back_tag(lines, "#MUSIC:%s;", in.GetMusicFile()); diff --git a/src/NotesWriterSSC.h b/src/NotesWriterSSC.h index 43da090fcb..c6fff4f85f 100644 --- a/src/NotesWriterSSC.h +++ b/src/NotesWriterSSC.h @@ -1,6 +1,8 @@ #ifndef NOTES_WRITER_SSC_H #define NOTES_WRITER_SSC_H +#include "Steps.h" + class Song; class Steps; /** @brief Writes a Song to an .SSC file. */ @@ -35,6 +37,7 @@ namespace NotesWriterSSC * @return its success or failure. */ bool WriteEditFileToMachine( const Song *pSong, Steps *pSteps, RString &sErrorOut ); RString MSDToString(MinaSD x); + RString SkillsetDiffsToString(SDiffs x); } #endif diff --git a/src/Song.cpp b/src/Song.cpp index 9feb070206..c8b06c742f 100644 --- a/src/Song.cpp +++ b/src/Song.cpp @@ -1129,7 +1129,7 @@ bool Song::SaveToSMFile() return NotesWriterSM::Write( sPath, *this, vpStepsToSave ); } -vector Song::GetStepsToSave(bool bSavingCache=true, string path="") +vector Song::GetStepsToSave(bool bSavingCache, string path) { vector vpStepsToSave; @@ -1172,7 +1172,7 @@ bool Song::SaveToSSCFile( const RString &sPath, bool bSavingCache, bool autosave if(bSavingCache || autosave) { - return NotesWriterSSC::Write(path, *this, vpStepsToSave, bSavingCache); + return SONGINDEX->CacheSong(*this, path); } if( !NotesWriterSSC::Write(path, *this, vpStepsToSave, bSavingCache) ) @@ -1225,23 +1225,7 @@ bool Song::SaveToETTFile(const RString &sPath, bool bSavingCache, bool autosave) if (!bSavingCache && !autosave && IsAFile(path)) FileCopy(path, path + ".old"); - vector vpStepsToSave; - FOREACH_CONST(Steps*, m_vpSteps, s) - { - Steps *pSteps = *s; - - // Only save steps that weren't loaded from a profile. - if (pSteps->WasLoadedFromProfile()) - continue; - - if (!bSavingCache) - pSteps->SetFilename(path); - vpStepsToSave.push_back(pSteps); - } - FOREACH_CONST(Steps*, m_UnknownStyleSteps, s) - { - vpStepsToSave.push_back(*s); - } + vector vpStepsToSave = GetStepsToSave(bSavingCache, sPath); if (bSavingCache || autosave) { @@ -1293,7 +1277,7 @@ bool Song::SaveToCacheFile() { return true; } - return SONGINDEX->SaveSong(*this, m_sSongDir); + return SONGINDEX->CacheSong(*this, m_sSongDir); SONGINDEX->AddCacheIndex(m_sSongDir, GetHashForDirectory(m_sSongDir)); const RString sPath = GetCacheFilePath(); return SaveToSSCFile(sPath, true); diff --git a/src/SongCacheIndex.cpp b/src/SongCacheIndex.cpp index d360b18fd6..c8743c932d 100644 --- a/src/SongCacheIndex.cpp +++ b/src/SongCacheIndex.cpp @@ -35,6 +35,7 @@ */ const string CACHE_INDEX = SpecialFiles::CACHE_DIR + "index.cache"; const string CACHE_DB = SpecialFiles::CACHE_DIR + "cache.db"; +const string CACHE_DB_VERSION = "1.0"; SongCacheIndex *SONGINDEX; // global and accessible from anywhere in our program @@ -72,10 +73,14 @@ SongCacheIndex::SongCacheIndex() int SongCacheIndex::InsertStepsTimingData(TimingData timing) { SQLite::Statement insertTimingData(*db, "INSERT INTO timingdatas VALUES (NULL, " + "?, ?, ?, " + "?, ?, ?, ?, " + "?, ?, ?, ?, ?)"); + /*SQLite::Statement insertTimingData(*db, "INSERT INTO timingdatas VALUES (NULL, " "OFFSET=?, BPMS=?, STOPS=?, " "DELAYS=?, WARPS=?, TIMESIGNATURESEGMENT=?, TICKCOUNTS=?, " - "COMBOS=?, SPEEDS=?, SCROLLS=?, FAKES=?, LABELS=?)"); - unsigned int timingDataIndex = 0; + "COMBOS=?, SPEEDS=?, SCROLLS=?, FAKES=?, LABELS=?)");*/ + unsigned int timingDataIndex = 1; insertTimingData.bind(timingDataIndex++, timing.m_fBeat0OffsetInSeconds); { vector const& segs = timing.GetTimingSegments(SEGMENT_BPM); @@ -211,7 +216,7 @@ int SongCacheIndex::InsertStepsTimingData(TimingData timing) const LabelSegment* segment = ToLabel(seg); if (!segment->GetLabel().empty()) { - labels.append(ssprintf("%.6f=%s", NoteRowToBeat(segment->GetRow()), segment->GetLabel())); + labels.append(ssprintf("%.6f=%s", NoteRowToBeat(segment->GetRow()), segment->GetLabel().c_str())); } } } @@ -223,19 +228,40 @@ int SongCacheIndex::InsertStepsTimingData(TimingData timing) int SongCacheIndex::InsertSteps(const Steps* pSteps, int songID) { + /* SQLite::Statement insertSteps(*db, "INSERT INTO steps VALUES (NULL, " "CHARTNAME=?, STEPSTYPE=?, DESCRIPTION=?, CHARTSTYLE=?, DIFFICULTY=?, " "METER=?, MSDVALUES=?, CHARTKEY=?, MUSIC=?, RADARVALUES=?, CREDIT=?, " - "TIMINGDATAID=?, DISPLAYBPM=?, STEPFILENAME=?, SONGID=?)"); + "TIMINGDATAID=?, DISPLAYBPMMIN=?, DISPLAYBPMMAX=?, STEPFILENAME=?, SONGID=?)"); + + db->exec("CREATE TABLE IF NOT EXISTS steps (id INTEGER PRIMARY KEY, " + "CHARTNAME TEXT, STEPSTYPE TEXT, DESCRIPTION TEXT, CHARTSTYLE TEXT, DIFFICULTY INTEGER, " + "METER INTEGER, OVERALL TEXT, STREAM TEXT, JUMPSTREAM TEXT, HANDSTREAM TEXT, " + "STAMINA TEXT, JACKSPEED TEXT, JACKSTAMINA TEXT, TECHNICAL TEXT, CHARTKEY TEXT, " + "MUSIC TEXT, RADARVALUES TEXT, CREDIT TEXT, " + "TIMINGDATAID INTEGER, DISPLAYBPMMIN FLOAT, DISPLAYBPMMAX FLOAT, STEPFILENAME TEXT, SONGID INTEGER, " + "CONSTRAINT fk_songid FOREIGN KEY (SONGID) REFERENCES songs(id), " + "CONSTRAINT fk_timingdataid FOREIGN KEY (TIMINGDATAID) REFERENCES songs(ID))"); + */ + SQLite::Statement insertSteps(*db, "INSERT INTO steps VALUES (NULL, " + "?, ?, ?, ?, ?, " + "?, ?, ?, ?, ?, " + "?, ?, ?, ?, ?, " + "?, ?, ?, " + "?, ?, ?, ?, ?)"); vector lines; - int stepsIndex = 0; + int stepsIndex = 1; insertSteps.bind(stepsIndex++, pSteps->GetChartName()); insertSteps.bind(stepsIndex++, pSteps->m_StepsTypeStr); insertSteps.bind(stepsIndex++, pSteps->GetDescription()); insertSteps.bind(stepsIndex++, pSteps->GetChartStyle()); insertSteps.bind(stepsIndex++, pSteps->GetDifficulty()); insertSteps.bind(stepsIndex++, pSteps->GetMeter()); - insertSteps.bind(stepsIndex++, NotesWriterSSC::MSDToString(pSteps->GetAllMSD()).c_str());//msdvalues + + MinaSD o = pSteps->GetAllMSD(); + for (size_t i = 0; i < NUM_Skillset; i++) + insertSteps.bind(stepsIndex++, NotesWriterSSC::SkillsetDiffsToString(o[i])); + insertSteps.bind(stepsIndex++, SmEscape(pSteps->GetChartKey()).c_str());//chartkey insertSteps.bind(stepsIndex++, pSteps->GetMusicFile());//musicfile @@ -262,22 +288,20 @@ int SongCacheIndex::InsertSteps(const Steps* pSteps, int songID) { case DISPLAY_BPM_ACTUAL: insertSteps.bind(stepsIndex++); - // write nothing + insertSteps.bind(stepsIndex++); break; case DISPLAY_BPM_SPECIFIED: { - float small = pSteps->GetMinBPM(); - float big = pSteps->GetMaxBPM(); - if (small == big) - insertSteps.bind(stepsIndex++, ssprintf("%.6f;", small)); - else - insertSteps.bind(stepsIndex++, ssprintf("%.6f:%.6f;", small, big)); + insertSteps.bind(stepsIndex++, pSteps->GetMinBPM()); + insertSteps.bind(stepsIndex++, pSteps->GetMaxBPM()); break; } case DISPLAY_BPM_RANDOM: - insertSteps.bind(stepsIndex++, "*"); + insertSteps.bind(stepsIndex++); + insertSteps.bind(stepsIndex++, 0.0); break; default: + insertSteps.bind(stepsIndex++); insertSteps.bind(stepsIndex++); break; } @@ -287,212 +311,218 @@ int SongCacheIndex::InsertSteps(const Steps* pSteps, int songID) return sqlite3_last_insert_rowid(db->getHandle()); } /* Save a song to the cache db*/ -bool SongCacheIndex::SaveSong(Song& song, string dir) +bool SongCacheIndex::CacheSong(Song& song, string dir) { - SQLite::Statement insertSong(*db, "INSERT INTO songs VALUES (NULL, " - "VERSION=?, TITLE=?, SUBTITLE=?, ARTIST=?, TITLETRANSLIT=?, " - "SUBTITLETRANSLIT=?, ARTISTTRANSLIT=?, GENRE=?, " - "ORIGIN=?, CREDIT=?, BANNER=?, BACKGROUND=?" - "PREVIEWVID=?, JACKET=?, CDIMAGE=?, DISCIMAGE=?, " - "LYRICSPATH=?, CDTITLE=?, MUSIC=?, PREVIEW=?, INSTRUMENTTRACK=?, " - "OFFSET=?, SAMPLESTART=?, SAMPLELENGTH=?, SELECTABLE=?, " - "DISPLAYBPM=?, BPMS=?, STOPS=?, DELAYS=?, WARPS=?, " - "TIMESIGNATURES=?, TICKCOUNTS=?, COMBOS=?, SPEEDS=?, " - "SCROLLS=?, FAKES=?, LABELS=?, LASTSECONDHINT=?, " - "BGCHANGESLAYER1=?, BGCHANGESLAYER2=?, FGCHANGES=?, " - "KEYSOUNDS=?, FIRSTSECOND=?, LASTSECOND=?, " - "SONGFILENAME=?, HASMUSIC=?, HASBANNER=?, MUSICLENGTH=?, DIRHASH=?)"); - unsigned int index = 0; - insertSong.bind(index++, STEPFILE_VERSION_NUMBER); - insertSong.bind(index++, song.m_sMainTitle); - insertSong.bind(index++, song.m_sSubTitle); - insertSong.bind(index++, song.m_sArtist); - insertSong.bind(index++, song.m_sMainTitleTranslit); - insertSong.bind(index++, song.m_sSubTitleTranslit); - insertSong.bind(index++, song.m_sArtistTranslit); - insertSong.bind(index++, song.m_sGenre); - insertSong.bind(index++, song.m_sOrigin); - insertSong.bind(index++, song.m_sCredit); - insertSong.bind(index++, song.m_sBannerFile); - insertSong.bind(index++, song.m_sBackgroundFile); - insertSong.bind(index++, song.m_sPreviewVidFile); - insertSong.bind(index++, song.m_sJacketFile); - insertSong.bind(index++, song.m_sCDFile); - insertSong.bind(index++, song.m_sDiscFile); - insertSong.bind(index++, song.m_sLyricsFile); - insertSong.bind(index++, song.m_sCDTitleFile); - insertSong.bind(index++, song.m_sMusicFile); - insertSong.bind(index++, song.m_PreviewFile); - auto vs = song.GetInstrumentTracksToVectorString(); - if (!vs.empty()) - { - std::string s = join(",", vs); - insertSong.bind(index++, s); - } - else { - insertSong.bind(index++); - } - insertSong.bind(index++, song.m_SongTiming.m_fBeat0OffsetInSeconds); - insertSong.bind(index++, song.m_fMusicSampleStartSeconds); - insertSong.bind(index++, song.m_fMusicSampleLengthSeconds); - //Selectable should be stored as int - switch (song.m_SelectionDisplay) - { - default: ASSERT_M(0, "An invalid selectable value was found for this song!"); // fall through - case Song::SHOW_ALWAYS: insertSong.bind(index++, 0); break; - case Song::SHOW_NEVER: insertSong.bind(index++, 1); break; - } - - switch (song.m_DisplayBPMType) - { - case DISPLAY_BPM_ACTUAL: - // write nothing(Both nulls) - insertSong.bind(index++); - insertSong.bind(index++); - break; - case DISPLAY_BPM_SPECIFIED: - if (song.m_fSpecifiedBPMMin == song.m_fSpecifiedBPMMax) + try { + SQLite::Statement insertSong(*db, "INSERT INTO songs VALUES (NULL, " + "?, ?, ?, ?, ?, " + "?, ?, ?, " + "?, ?, ?, ?, " + "?, ?, ?, ?, " + "?, ?, ?, ?, ?, " + "?, ?, ?, ?, " + "?, ?, ?, ?, ?, ?, " + "?, ?, ?, ?, " + "?, ?, ?, ?, " + "?, ?, ?, " + "?, ?, ?, " + "?, ?, ?, ?, ?, ?)"); + unsigned int index = 1; + insertSong.bind(index++, STEPFILE_VERSION_NUMBER); + insertSong.bind(index++, song.m_sMainTitle); + insertSong.bind(index++, song.m_sSubTitle); + insertSong.bind(index++, song.m_sArtist); + insertSong.bind(index++, song.m_sMainTitleTranslit); + insertSong.bind(index++, song.m_sSubTitleTranslit); + insertSong.bind(index++, song.m_sArtistTranslit); + insertSong.bind(index++, song.m_sGenre); + insertSong.bind(index++, song.m_sOrigin); + insertSong.bind(index++, song.m_sCredit); + insertSong.bind(index++, song.m_sBannerFile); + insertSong.bind(index++, song.m_sBackgroundFile); + insertSong.bind(index++, song.m_sPreviewVidFile); + insertSong.bind(index++, song.m_sJacketFile); + insertSong.bind(index++, song.m_sCDFile); + insertSong.bind(index++, song.m_sDiscFile); + insertSong.bind(index++, song.m_sLyricsFile); + insertSong.bind(index++, song.m_sCDTitleFile); + insertSong.bind(index++, song.m_sMusicFile); + insertSong.bind(index++, song.m_PreviewFile); + auto vs = song.GetInstrumentTracksToVectorString(); + if (!vs.empty()) { - insertSong.bind(index++, song.m_fSpecifiedBPMMin); - insertSong.bind(index++, song.m_fSpecifiedBPMMin); + std::string s = join(",", vs); + insertSong.bind(index++, s); } - else + else { + insertSong.bind(index++); + } + insertSong.bind(index++, song.m_SongTiming.m_fBeat0OffsetInSeconds); + insertSong.bind(index++, song.m_fMusicSampleStartSeconds); + insertSong.bind(index++, song.m_fMusicSampleLengthSeconds); + //Selectable should be stored as int + switch (song.m_SelectionDisplay) { - insertSong.bind(index++, song.m_fSpecifiedBPMMin); - insertSong.bind(index++, song.m_fSpecifiedBPMMax); + default: ASSERT_M(0, "An invalid selectable value was found for this song!"); // fall through + case Song::SHOW_ALWAYS: insertSong.bind(index++, 0); break; + case Song::SHOW_NEVER: insertSong.bind(index++, 1); break; } - break; - case DISPLAY_BPM_RANDOM: - //Write only one as null - insertSong.bind(index++); - insertSong.bind(index++, 0.0); - break; - default: - insertSong.bind(index++); - insertSong.bind(index++); - break; - } - insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_BPM, 3))); - insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_STOP, 3))); - insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_DELAY, 3))); - insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_WARP, 3))); - insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_TIME_SIG, 3))); - insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_TICKCOUNT, 3))); - insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_COMBO, 3))); - insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_SPEED, 3))); - insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_SCROLL, 3))); - insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_FAKE, 3))); - insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_LABEL, 3))); - if (song.GetSpecifiedLastSecond() > 0) - { - insertSong.bind(index++, song.GetSpecifiedLastSecond()); - } - else { - insertSong.bind(index++); - } - /* Theres only 2 background layers - // @brief The different background layers available. - enum BackgroundLayer - { - BACKGROUND_LAYER_1, - BACKGROUND_LAYER_2, - //BACKGROUND_LAYER_3, // StepCollection get - NUM_BackgroundLayer, - BACKGROUND_LAYER_Invalid - }; */ - FOREACH_BackgroundLayer(b) - { - string bgchanges = ""; - if (song.GetBackgroundChanges(b).empty()) + + switch (song.m_DisplayBPMType) { + case DISPLAY_BPM_ACTUAL: + // write nothing(Both nulls) + insertSong.bind(index++); insertSong.bind(index++); - continue; // skip + break; + case DISPLAY_BPM_SPECIFIED: + if (song.m_fSpecifiedBPMMin == song.m_fSpecifiedBPMMax) + { + insertSong.bind(index++, song.m_fSpecifiedBPMMin); + insertSong.bind(index++, song.m_fSpecifiedBPMMin); + } + else + { + insertSong.bind(index++, song.m_fSpecifiedBPMMin); + insertSong.bind(index++, song.m_fSpecifiedBPMMax); + } + break; + case DISPLAY_BPM_RANDOM: + //Write only one as null + insertSong.bind(index++); + insertSong.bind(index++, 0.0); + break; + default: + insertSong.bind(index++); + insertSong.bind(index++); + break; } - for (auto &bgc : song.GetBackgroundChanges(b)) + insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_BPM, 3))); + insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_STOP, 3))); + insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_DELAY, 3))); + insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_WARP, 3))); + insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_TIME_SIG, 3))); + insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_TICKCOUNT, 3))); + insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_COMBO, 3))); + insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_SPEED, 3))); + insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_SCROLL, 3))); + insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_FAKE, 3))); + insertSong.bind(index++, join(",\r\n", song.m_SongTiming.ToVectorString(SEGMENT_LABEL, 3))); + if (song.GetSpecifiedLastSecond() > 0) { - bgchanges.append(bgc.ToString() + ","); + insertSong.bind(index++, song.GetSpecifiedLastSecond()); } - - /* If there's an animation plan at all, add a dummy "-nosongbg-" tag to - * indicate that this file doesn't want a song BG entry added at the end. - * See SSCLoader::TidyUpData. This tag will be removed on load. Add it - * at a very high beat, so it won't cause problems if loaded in older versions. */ - if (b == 0 && !song.GetBackgroundChanges(b).empty()) + else { + insertSong.bind(index++); + } + /* Theres only 2 background layers + // @brief The different background layers available. + enum BackgroundLayer + { + BACKGROUND_LAYER_1, + BACKGROUND_LAYER_2, + //BACKGROUND_LAYER_3, // StepCollection get + NUM_BackgroundLayer, + BACKGROUND_LAYER_Invalid + }; */ + FOREACH_BackgroundLayer(b) { - bgchanges.append("99999=-nosongbg-=1.000=0=0=0"); + string bgchanges = ""; + if (song.GetBackgroundChanges(b).empty()) + { + insertSong.bind(index++); + continue; // skip + } + for (auto &bgc : song.GetBackgroundChanges(b)) + { + bgchanges.append(bgc.ToString() + ","); + } + + /* If there's an animation plan at all, add a dummy "-nosongbg-" tag to + * indicate that this file doesn't want a song BG entry added at the end. + * See SSCLoader::TidyUpData. This tag will be removed on load. Add it + * at a very high beat, so it won't cause problems if loaded in older versions. */ + if (b == 0 && !song.GetBackgroundChanges(b).empty()) + { + bgchanges.append("99999=-nosongbg-=1.000=0=0=0"); + } + insertSong.bind(index++, bgchanges); } - insertSong.bind(index++, bgchanges); - } - if (song.GetForegroundChanges().size()) - { - string fgchanges; - for (auto const &bgc : song.GetForegroundChanges()) + if (song.GetForegroundChanges().size()) { - fgchanges.append(bgc.ToString() + ","); + string fgchanges; + for (auto const &bgc : song.GetForegroundChanges()) + { + fgchanges.append(bgc.ToString() + ","); + } + insertSong.bind(index++, fgchanges); + } + else { + insertSong.bind(index++); } - insertSong.bind(index++, fgchanges); - } - else { - insertSong.bind(index++); - } - if (!song.m_vsKeysoundFile.empty()) { - for (unsigned i = 0; i < song.m_vsKeysoundFile.size(); i++) - { - if (i == 0 && song.m_vsKeysoundFile[i].size() > 0 && song.m_vsKeysoundFile[i][0] == '#') + if (!song.m_vsKeysoundFile.empty()) { + string keysounds=""; + for (unsigned i = 0; i < song.m_vsKeysoundFile.size(); i++) { - insertSong.bind(index++, song.m_vsKeysoundFile[i].substr(1, song.m_vsKeysoundFile[i].size()-1)); + keysounds.append(song.m_vsKeysoundFile[i]); + if (i != song.m_vsKeysoundFile.size() - 1) + { + keysounds.append(","); + } } - else { - insertSong.bind(index++, song.m_vsKeysoundFile[i]); + insertSong.bind(index++, keysounds); + } + else { + insertSong.bind(index++); + } + insertSong.bind(index++, song.GetFirstSecond()); + insertSong.bind(index++, song.GetLastSecond()); + insertSong.bind(index++, song.m_sSongFileName.c_str()); + insertSong.bind(index++, song.m_bHasMusic); + insertSong.bind(index++, song.m_bHasBanner); + insertSong.bind(index++, song.m_fMusicLengthSeconds); + insertSong.bind(index++, GetHashForDirectory(song.GetSongDir())); + insertSong.bind(index++, song.GetSongDir()); + insertSong.exec(); + int songID = sqlite3_last_insert_rowid(db->getHandle()); + vector vpStepsToSave = song.GetStepsToSave(); + FOREACH_CONST(Steps*, vpStepsToSave, s) + { + const Steps* pSteps = *s; + if (pSteps->GetChartKey() == "") { // Avoid writing cache tags for invalid chartkey files(empty steps) -Mina + LOG->Info("Not caching empty difficulty in file %s", dir.c_str()); + continue; } + int stepsID = InsertSteps(pSteps, songID); } + return true; } - else { - insertSong.bind(index++); - } - insertSong.bind(index++, song.GetFirstSecond()); - insertSong.bind(index++, song.GetLastSecond()); - insertSong.bind(index++, song.m_sSongFileName.c_str()); - insertSong.bind(index++, song.m_bHasMusic); - insertSong.bind(index++, song.m_bHasBanner); - insertSong.bind(index++, song.m_fMusicLengthSeconds); - insertSong.bind(index++, GetHashForDirectory(song.GetSongDir())); - insertSong.exec(); - int songID = sqlite3_last_insert_rowid(db->getHandle()); - vector vpStepsToSave = song.GetStepsToSave(); - FOREACH_CONST(Steps*, vpStepsToSave, s) + catch (std::exception& e) { - const Steps* pSteps = *s; - if (pSteps->GetChartKey() == "") { // Avoid writing cache tags for invalid chartkey files(empty steps) -Mina - LOG->Info("Not caching empty difficulty in file %s", dir.c_str()); - continue; - } - int stepsID = InsertSteps(pSteps, songID); + LOG->Trace("Error saving song %s to cache db: %s", dir, e.what()); + return false; } - return true; } /* Reset the DB/ Must be open already */ void SongCacheIndex::DeleteDB() { - if (db == nullptr) - return; - SQLite::Statement qTables(*db, "SELECT name FROM sqlite_master WHERE type='table'"); - qTables.exec(); - while (qTables.executeStep()) - { - string table = static_cast(qTables.getColumn(0)); - db->exec("DROP TABLE IF EXISTS " + table); - } - LOG->Trace("Cache database is out of date. Deleting all cache files."); - db->exec("VACUUM"); //Shrink to fit + if(db!= nullptr) + delete db; + FILEMAN->Remove(CACHE_DB); + db = new SQLite::Database(FILEMAN->ResolvePath(CACHE_DB), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); + return; } void SongCacheIndex::ResetDB() { - ResetDB(); + DeleteDB(); + CreateDBTables(); +} +void SongCacheIndex::CreateDBTables() +{ db->exec("CREATE TABLE IF NOT EXISTS dbinfo (ID INTEGER PRIMARY KEY, " "VERSION INTEGER)"); db->exec("CREATE TABLE IF NOT EXISTS timingdatas (ID INTEGER PRIMARY KEY, " @@ -505,19 +535,28 @@ void SongCacheIndex::ResetDB() "ORIGIN TEXT, CREDIT TEXT, BANNER TEXT, BACKGROUND TEXT, " "PREVIEWVID TEXT, JACKET TEXT, CDIMAGE TEXT, DISCIMAGE TEXT, " "LYRICSPATH TEXT, CDTITLE TEXT, MUSIC TEXT, PREVIEW TEXT, INSTRUMENTTRACK TEXT, " - "OFFSET FLOAT, SAMPLESTART FLOAT, SAMPLELENGTH FLOAT, SELECTABLE TEXT, " - "DISPLAYBPMMIN FLOAT, DISPLAYBPM MAX FLOAT, BPMS TEXT, STOPS TEXT, DELAYS TEXT, WARPS TEXT, " + "OFFSET FLOAT, SAMPLESTART FLOAT, SAMPLELENGTH FLOAT, SELECTABLE INTEGER, " + "DISPLAYBPMMIN FLOAT, DISPLAYBPMMAX FLOAT, BPMS TEXT, STOPS TEXT, DELAYS TEXT, WARPS TEXT, " "TIMESIGNATURES TEXT, TICKCOUNTS TEXT, COMBOS TEXT, SPEEDS TEXT, " "SCROLLS TEXT, FAKES TEXT, LABELS TEXT, LASTSECONDHINT FLOAT, " "BGCHANGESLAYER1 TEXT, BGCHANGESLAYER2 TEXT, FGCHANGES TEXT, " "KEYSOUNDS TEXT, FIRSTSECOND FLOAT, LASTSECOND FLOAT, " - "SONGFILENAME TEXT, HASMUSIC TEXT, HASBANNER TEXT, MUSICLENGTH FLOAT, DIRHASH INTEGER)"); + "SONGFILENAME TEXT, HASMUSIC INTEGER, HASBANNER INTEGER, MUSICLENGTH FLOAT, DIRHASH INTEGER, DIR TEXT)"); db->exec("CREATE TABLE IF NOT EXISTS steps (id INTEGER PRIMARY KEY, " "CHARTNAME TEXT, STEPSTYPE TEXT, DESCRIPTION TEXT, CHARTSTYLE TEXT, DIFFICULTY INTEGER, " - "METER INTEGER, MSDVALUES TEXT, CHARTKEY TEXT, MUSIC TEXT, RADARVALUES TEXT, CREDIT TEXT, " - "TIMINGDATAID TEXT, DISPLAYBPM TEXT, STEPFILENAME TEXT, SONGID INTEGER, " + "METER INTEGER, OVERALL TEXT, STREAM TEXT, JUMPSTREAM TEXT, HANDSTREAM TEXT, " + "STAMINA TEXT, JACKSPEED TEXT, JACKSTAMINA TEXT, TECHNICAL TEXT, CHARTKEY TEXT, " + "MUSIC TEXT, RADARVALUES TEXT, CREDIT TEXT, " + "TIMINGDATAID INTEGER, DISPLAYBPMMIN FLOAT, DISPLAYBPMMAX FLOAT, STEPFILENAME TEXT, SONGID INTEGER, " "CONSTRAINT fk_songid FOREIGN KEY (SONGID) REFERENCES songs(id), " "CONSTRAINT fk_timingdataid FOREIGN KEY (TIMINGDATAID) REFERENCES songs(ID))"); + db->exec("CREATE INDEX IF NOT EXISTS idx_dirs " + "ON songs(DIR, DIRHASH)"); + db->exec("CREATE INDEX IF NOT EXISTS idx_timingdatas " + "ON timingdatas(ID)"); + db->exec("CREATE INDEX IF NOT EXISTS idx_steps " + "ON steps(SONGID)"); + db->exec("INSERT INTO dbinfo VALUES (NULL, " + CACHE_DB_VERSION + ")"); } /* Returns weather or not the db had valid data*/ bool SongCacheIndex::OpenDB() @@ -525,9 +564,17 @@ bool SongCacheIndex::OpenDB() bool ret = IsAFile(CACHE_DB); //Try to open ane existing db try { - db = new SQLite::Database(FILEMAN->ResolvePath(CACHE_INDEX), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE); + db = new SQLite::Database(FILEMAN->ResolvePath(CACHE_DB), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE | SQLITE_OPEN_FULLMUTEX); + StartTransaction(); + db->exec("CREATE INDEX IF NOT EXISTS idx_dirs " + "ON songs(DIR, DIRHASH)"); + db->exec("CREATE INDEX IF NOT EXISTS idx_timingdatas " + "ON timingdatas(ID)"); + db->exec("CREATE INDEX IF NOT EXISTS idx_steps " + "ON steps(SONGID)"); if (!ret) { ResetDB(); + FinishTransaction(); return false; } SQLite::Statement qDBInfo(*db, "SELECT * FROM dbinfo"); @@ -535,20 +582,22 @@ bool SongCacheIndex::OpenDB() //Should only have one row so no executeStep loop if (!qDBInfo.executeStep()) { ResetDB(); + FinishTransaction(); return false; } - int iCacheVersion = -1; - iCacheVersion = qDBInfo.getColumn(0); - if (iCacheVersion == FILE_CACHE_VERSION) + string cacheVersion = ""; + cacheVersion = static_cast(qDBInfo.getColumn(1)); + if (cacheVersion == CACHE_DB_VERSION) { + FinishTransaction(); return true; - ResetDB(); + } } catch (std::exception& e) { LOG->Trace("Error reading cache db: %s", e.what()); - db = nullptr; - return false; } + FinishTransaction(); + ResetDB(); return false; } @@ -637,234 +686,258 @@ RString SongCacheIndex::MangleName(const RString &Name) return ret; } +void SongCacheIndex::StartTransaction() +{ + if (curTransaction != nullptr) + return; + curTransaction = new SQLite::Transaction(*db); +} +void SongCacheIndex::FinishTransaction() +{ + curTransaction->commit(); + delete curTransaction; + curTransaction = nullptr; + return; +} + /* Load a song from Cache DB Returns true if it was loaded**/ bool SongCacheIndex::LoadSongFromCache(Song* song, string dir) { - db->exec("CREATE TABLE IF NOT EXISTS songs (ID INTEGER PRIMARY KEY, " - "VERSION TEXT, TITLE TEXT, SUBTITLE TEXT, ARTIST TEXT, TITLETRANSLIT TEXT, " - "SUBTITLETRANSLIT TEXT, ARTISTTRANSLIT TEXT, GENRE TEXT, " - "ORIGIN TEXT, CREDIT TEXT, BANNER TEXT, BACKGROUND TEXT, " - "PREVIEWVID TEXT, JACKET TEXT, CDIMAGE TEXT, DISCIMAGE TEXT, " - "LYRICSPATH TEXT, CDTITLE TEXT, MUSIC TEXT, PREVIEW TEXT, INSTRUMENTTRACK TEXT, " - "OFFSET FLOAT, SAMPLESTART FLOAT, SAMPLELENGTH FLOAT, SELECTABLE TEXT, " - "DISPLAYBPMMIN FLOAT, DISPLAYBPM MAX FLOAT, BPMS TEXT, STOPS TEXT, DELAYS TEXT, WARPS TEXT, " - "TIMESIGNATURES TEXT, TICKCOUNTS TEXT, COMBOS TEXT, SPEEDS TEXT, " - "SCROLLS TEXT, FAKES TEXT, LABELS TEXT, LASTSECONDHINT FLOAT, " - "BGCHANGESLAYER1 TEXT, BGCHANGESLAYER2 TEXT, FGCHANGES TEXT, " - "KEYSOUNDS TEXT, FIRSTSECOND FLOAT, LASTSECOND FLOAT, " - "SONGFILENAME TEXT, HASMUSIC TEXT, HASBANNER TEXT, MUSICLENGTH FLOAT, DIRHASH INTEGER)"); - - SQLite::Statement query(*db, "SELECT * FROM songs WHERE DIR=? AND DIRHASH=?"); - query.bind(1, dir); - query.bind(2, GetHashForDirectory(song->GetSongDir())); - - //No cache entry => return false - if (!query.tryExecuteStep()) - return false; - - //SSC::StepsTagInfo reused_steps_info(&*song, &out, dir, true); - SSCLoader loader; - int songid = query.getColumn(0); - int index = 1; - song->m_fVersion = static_cast(query.getColumn(index++)); - song->m_sMainTitle = static_cast(query.getColumn(index++)); - song->m_sSubTitle = static_cast(query.getColumn(index++)); - song->m_sArtist = static_cast(query.getColumn(index++)); - song->m_sMainTitleTranslit = static_cast(query.getColumn(index++)); - song->m_sSubTitleTranslit = static_cast(query.getColumn(index++)); - song->m_sArtistTranslit = static_cast(query.getColumn(index++)); - song->m_sGenre = static_cast(query.getColumn(index++)); - song->m_sOrigin = static_cast(query.getColumn(index++)); - song->m_sCredit = static_cast(query.getColumn(index++)); - Trim(song->m_sCredit); - song->m_sBannerFile = static_cast(query.getColumn(index++)); - song->m_sBackgroundFile = static_cast(query.getColumn(index++)); - song->m_sPreviewVidFile = static_cast(query.getColumn(index++)); - song->m_sJacketFile = static_cast(query.getColumn(index++)); - song->m_sCDFile = static_cast(query.getColumn(index++)); - song->m_sDiscFile = static_cast(query.getColumn(index++)); - song->m_sLyricsFile = static_cast(query.getColumn(index++)); - song->m_sCDTitleFile = static_cast(query.getColumn(index++)); - song->m_sMusicFile = static_cast(query.getColumn(index++)); - song->m_PreviewFile = static_cast(query.getColumn(index++)); - loader.ProcessInstrumentTracks(*song, static_cast(query.getColumn(index++))); - song->SetSpecifiedLastSecond(static_cast(query.getColumn(index++))); - song->m_fMusicSampleStartSeconds = static_cast(query.getColumn(index++)); - song->m_fMusicSampleLengthSeconds = static_cast(query.getColumn(index++)); - song->m_SongTiming.m_fBeat0OffsetInSeconds = static_cast(query.getColumn(index++)); + try { + SQLite::Statement query(*db, "SELECT * FROM songs WHERE DIR=? AND DIRHASH=?"); + query.bind(1, dir); + query.bind(2, GetHashForDirectory(song->GetSongDir())); - int selection = static_cast(query.getColumn(index++)); - if (selection == 0) - song->m_SelectionDisplay = song->SHOW_ALWAYS; - else - song->m_SelectionDisplay = song->SHOW_NEVER; + //No cache entry => return false + if (!query.executeStep()) + return false; - int bpmminIndex = index++; - int bpmmaxIndex = index++; - float BPMmin = static_cast(query.getColumn(bpmminIndex)); - float BPMmax = static_cast(query.getColumn(bpmmaxIndex)); - if (query.isColumnNull(bpmminIndex) || query.isColumnNull(bpmmaxIndex)) - { - if (query.isColumnNull(bpmminIndex) && query.isColumnNull(bpmmaxIndex)) - song->m_DisplayBPMType = DISPLAY_BPM_RANDOM; + //SSC::StepsTagInfo reused_steps_info(&*song, &out, dir, true); + SSCLoader loader; + int songid = query.getColumn(0); + int index = 1; + song->m_fVersion = static_cast(query.getColumn(index++)); + song->m_sMainTitle = static_cast(query.getColumn(index++)); + song->m_sSubTitle = static_cast(query.getColumn(index++)); + song->m_sArtist = static_cast(query.getColumn(index++)); + song->m_sMainTitleTranslit = static_cast(query.getColumn(index++)); + song->m_sSubTitleTranslit = static_cast(query.getColumn(index++)); + song->m_sArtistTranslit = static_cast(query.getColumn(index++)); + song->m_sGenre = static_cast(query.getColumn(index++)); + song->m_sOrigin = static_cast(query.getColumn(index++)); + song->m_sCredit = static_cast(query.getColumn(index++)); + Trim(song->m_sCredit); + song->m_sBannerFile = static_cast(query.getColumn(index++)); + song->m_sBackgroundFile = static_cast(query.getColumn(index++)); + song->m_sPreviewVidFile = static_cast(query.getColumn(index++)); + song->m_sJacketFile = static_cast(query.getColumn(index++)); + song->m_sCDFile = static_cast(query.getColumn(index++)); + song->m_sDiscFile = static_cast(query.getColumn(index++)); + song->m_sLyricsFile = static_cast(query.getColumn(index++)); + song->m_sCDTitleFile = static_cast(query.getColumn(index++)); + song->m_sMusicFile = static_cast(query.getColumn(index++)); + song->m_PreviewFile = static_cast(query.getColumn(index++)); + loader.ProcessInstrumentTracks(*song, static_cast(query.getColumn(index++))); + song->m_SongTiming.m_fBeat0OffsetInSeconds = static_cast(query.getColumn(index++)); + song->m_fMusicSampleStartSeconds = static_cast(query.getColumn(index++)); + song->m_fMusicSampleLengthSeconds = static_cast(query.getColumn(index++)); + + int selection = static_cast(query.getColumn(index++)); + if (selection == 0) + song->m_SelectionDisplay = song->SHOW_ALWAYS; else - song->m_DisplayBPMType = DISPLAY_BPM_ACTUAL; - } - else - { - song->m_DisplayBPMType = DISPLAY_BPM_SPECIFIED; - song->m_fSpecifiedBPMMin = BPMmin; - song->m_fSpecifiedBPMMax = BPMmax; - } + song->m_SelectionDisplay = song->SHOW_NEVER; - loader.ProcessBPMs(song->m_SongTiming, song->GetMainTitle(), static_cast(query.getColumn(index++))); - loader.ProcessStops(song->m_SongTiming, song->GetMainTitle(), static_cast(query.getColumn(index++))); - loader.ProcessDelays(song->m_SongTiming, static_cast(query.getColumn(index++))); - loader.ProcessWarps(song->m_SongTiming, static_cast(query.getColumn(index++)), song->m_fVersion, song->GetMainTitle()); - loader.ProcessTimeSignatures(song->m_SongTiming, static_cast(query.getColumn(index++))); - loader.ProcessTickcounts(song->m_SongTiming, static_cast(query.getColumn(index++))); - loader.ProcessCombos(song->m_SongTiming, static_cast(query.getColumn(index++))); - loader.ProcessSpeeds(song->m_SongTiming, static_cast(query.getColumn(index++))); - loader.ProcessScrolls(song->m_SongTiming, song->GetMainTitle(), static_cast(query.getColumn(index++))); - loader.ProcessFakes(song->m_SongTiming, static_cast(query.getColumn(index++))); - loader.ProcessLabels(song->m_SongTiming, song->GetMainTitle(), static_cast(query.getColumn(index++))); - - song->SetSpecifiedLastSecond(static_cast(query.getColumn(index++))); - - string animations = static_cast(query.getColumn(index++)); - string animationstwo = static_cast(query.getColumn(index++)); - loader.ProcessBGChanges(*song, animations, - dir, animationstwo); - vector aFGChangeExpressions; - split(static_cast(query.getColumn(index++)), ",", aFGChangeExpressions); - - for (size_t b = 0; b < aFGChangeExpressions.size(); ++b) - { - BackgroundChange change; - if (loader.LoadFromBGChangesString(change, aFGChangeExpressions[b])) + int bpmminIndex = index++; + int bpmmaxIndex = index++; + float BPMmin = static_cast(query.getColumn(bpmminIndex)); + float BPMmax = static_cast(query.getColumn(bpmmaxIndex)); + if (query.isColumnNull(bpmminIndex) || query.isColumnNull(bpmmaxIndex)) { - song->AddForegroundChange(change); + if (query.isColumnNull(bpmminIndex) && query.isColumnNull(bpmmaxIndex)) + song->m_DisplayBPMType = DISPLAY_BPM_RANDOM; + else + song->m_DisplayBPMType = DISPLAY_BPM_ACTUAL; + } + else + { + song->m_DisplayBPMType = DISPLAY_BPM_SPECIFIED; + song->m_fSpecifiedBPMMin = BPMmin; + song->m_fSpecifiedBPMMax = BPMmax; } - } - RString keysounds = static_cast(query.getColumn(index++)); - if (keysounds.length() >= 2 && keysounds.substr(0, 2) == "\\#") - { - keysounds = keysounds.substr(1); - } - split(keysounds, ",", song->m_vsKeysoundFile); - song->SetFirstSecond(static_cast(query.getColumn(index++))); - song->SetLastSecond(static_cast(query.getColumn(index++))); - song->m_sSongFileName = static_cast(query.getColumn(index++)); - song->m_bHasMusic = static_cast(query.getColumn(index++)) != 0; - song->m_bHasBanner = static_cast(query.getColumn(index++)) != 0; - song->m_fMusicLengthSeconds = static_cast(query.getColumn(index++)); - - Steps* pNewNotes = nullptr; - SQLite::Statement qSteps(*db, "SELECT * FROM steps WHERE SONGID=" + to_string(songid)); - - while (qSteps.tryExecuteStep()) { - int stepsIndex = 0; - //state = GETTING_STEP_INFO; - pNewNotes = song->CreateSteps(); - pNewNotes->SetFilename(dir); - TimingData stepsTiming = TimingData(song->m_SongTiming.m_fBeat0OffsetInSeconds); - song->m_fVersion = static_cast(qSteps.getColumn(stepsIndex++)); - RString chartName = static_cast(qSteps.getColumn(stepsIndex++)); - Trim(chartName); - pNewNotes->SetChartName(chartName); - string stepsType = static_cast(qSteps.getColumn(stepsIndex++)); - pNewNotes->m_StepsType = GAMEMAN->StringToStepsType(stepsType); - pNewNotes->m_StepsTypeStr = stepsType; - pNewNotes->SetChartStyle(static_cast(qSteps.getColumn(stepsIndex++))); - RString description = static_cast(qSteps.getColumn(stepsIndex++)); - Trim(description); - pNewNotes->SetDescription(description); - pNewNotes->SetDifficulty(static_cast(static_cast(qSteps.getColumn(stepsIndex++)))); - pNewNotes->SetMeter(static_cast(qSteps.getColumn(stepsIndex++))); - pNewNotes->SetMusicFile(static_cast(qSteps.getColumn(stepsIndex++))); - string radarValues = static_cast(qSteps.getColumn(stepsIndex++)); - vector values; - split(radarValues, ",", values, true); - RadarValues rv; - rv.Zero(); - for (size_t i = 0; i < NUM_RadarCategory; ++i) - rv[i] = StringToInt(values[i]); - pNewNotes->SetCachedRadarValues(rv); - pNewNotes->SetCredit(static_cast(qSteps.getColumn(stepsIndex++))); - - /* If this is called, the chart does not use the same attacks - * as the Song's timing. No other changes are required. */ - int bpmminIndex = stepsIndex++; - int bpmmaxIndex = stepsIndex++; - float BPMmin = static_cast(qSteps.getColumn(bpmminIndex)); - float BPMmax = static_cast(qSteps.getColumn(bpmmaxIndex)); - if (qSteps.isColumnNull(bpmminIndex) || qSteps.isColumnNull(bpmmaxIndex)) + loader.ProcessBPMs(song->m_SongTiming, song->GetMainTitle(), static_cast(query.getColumn(index++))); + loader.ProcessStops(song->m_SongTiming, song->GetMainTitle(), static_cast(query.getColumn(index++))); + loader.ProcessDelays(song->m_SongTiming, static_cast(query.getColumn(index++))); + loader.ProcessWarps(song->m_SongTiming, static_cast(query.getColumn(index++)), song->m_fVersion, song->GetMainTitle()); + loader.ProcessTimeSignatures(song->m_SongTiming, static_cast(query.getColumn(index++))); + loader.ProcessTickcounts(song->m_SongTiming, static_cast(query.getColumn(index++))); + loader.ProcessCombos(song->m_SongTiming, static_cast(query.getColumn(index++))); + loader.ProcessSpeeds(song->m_SongTiming, static_cast(query.getColumn(index++))); + loader.ProcessScrolls(song->m_SongTiming, song->GetMainTitle(), static_cast(query.getColumn(index++))); + loader.ProcessFakes(song->m_SongTiming, static_cast(query.getColumn(index++))); + loader.ProcessLabels(song->m_SongTiming, song->GetMainTitle(), static_cast(query.getColumn(index++))); + + song->SetSpecifiedLastSecond(static_cast(query.getColumn(index++))); + + string animations = static_cast(query.getColumn(index++)); + string animationstwo = static_cast(query.getColumn(index++)); + loader.ProcessBGChanges(*song, animations, + dir, animationstwo); + vector aFGChangeExpressions; + split(static_cast(query.getColumn(index++)), ",", aFGChangeExpressions); + + for (size_t b = 0; b < aFGChangeExpressions.size(); ++b) { - if (qSteps.isColumnNull(bpmminIndex) && qSteps.isColumnNull(bpmmaxIndex)) - pNewNotes->SetDisplayBPM(DISPLAY_BPM_RANDOM); - else - pNewNotes->SetDisplayBPM(DISPLAY_BPM_ACTUAL); + BackgroundChange change; + if (loader.LoadFromBGChangesString(change, aFGChangeExpressions[b])) + { + song->AddForegroundChange(change); + } } - else + RString keysounds = static_cast(query.getColumn(index++)); + if (keysounds.length() >= 2 && keysounds.substr(0, 2) == "\\#") { - pNewNotes->SetDisplayBPM(DISPLAY_BPM_SPECIFIED); - pNewNotes->SetMinBPM(BPMmin); - pNewNotes->SetMaxBPM(BPMmax); + keysounds = keysounds.substr(1); } - pNewNotes->SetChartKey(static_cast(qSteps.getColumn(stepsIndex++))); - MinaSD o; - for (size_t i = 0; i <= NUM_Skillset; i++) - o.emplace_back(SSC::msdsplit(static_cast(qSteps.getColumn(stepsIndex++)))); - pNewNotes->SetAllMSD(o); - - if(!qSteps.isColumnNull(stepsIndex+1)) { - int timingID = qSteps.getColumn(stepsIndex++); - SQLite::Statement qTiming(*db, "SELECT * FROM timingdatas WHERE STEPSID=" + to_string(timingID)); - if (qTiming.tryExecuteStep()) { - //Load timing data - SSCLoader::ProcessBPMs(stepsTiming, static_cast(qSteps.getColumn(stepsIndex++)), dir); - //steps_tag_handlers["BPMS"] = &SetStepsBPMs; - SSCLoader::ProcessStops(stepsTiming, static_cast(qSteps.getColumn(stepsIndex++)), dir); - //steps_tag_handlers["STOPS"] = &SetStepsStops; - SSCLoader::ProcessDelays(stepsTiming, static_cast(qSteps.getColumn(stepsIndex++)), dir); - //steps_tag_handlers["DELAYS"] = &SetStepsDelays; - SSCLoader::ProcessTimeSignatures(stepsTiming, static_cast(qSteps.getColumn(stepsIndex++)), dir); - //steps_tag_handlers["TIMESIGNATURES"] = &SetStepsTimeSignatures; - SSCLoader::ProcessTickcounts(stepsTiming, static_cast(qSteps.getColumn(stepsIndex++)), dir); - //steps_tag_handlers["TICKCOUNTS"] = &SetStepsTickCounts; - SSCLoader::ProcessCombos(stepsTiming, static_cast(qSteps.getColumn(stepsIndex++)), dir); - //steps_tag_handlers["COMBOS"] = &SetStepsCombos; - SSCLoader::ProcessWarps(stepsTiming, static_cast(qSteps.getColumn(stepsIndex++)), song->m_fVersion, dir); - //steps_tag_handlers["WARPS"] = &SetStepsWarps; - SSCLoader::ProcessSpeeds(stepsTiming, static_cast(qSteps.getColumn(stepsIndex++)), dir); - //steps_tag_handlers["SPEEDS"] = &SetStepsSpeeds; - SSCLoader::ProcessScrolls(stepsTiming, static_cast(qSteps.getColumn(stepsIndex++)), dir); - //steps_tag_handlers["SCROLLS"] = &SetStepsScrolls; - SSCLoader::ProcessFakes(stepsTiming, static_cast(qSteps.getColumn(stepsIndex++)), dir); - //steps_tag_handlers["FAKES"] = &SetStepsFakes; - SSCLoader::ProcessLabels(stepsTiming, static_cast(qSteps.getColumn(stepsIndex++)), dir); - stepsTiming.m_fBeat0OffsetInSeconds = static_cast(qTiming.getColumn(stepsIndex++)); + split(keysounds, ",", song->m_vsKeysoundFile); + song->SetFirstSecond(static_cast(query.getColumn(index++))); + song->SetLastSecond(static_cast(query.getColumn(index++))); + song->m_sSongFileName = static_cast(query.getColumn(index++)); + song->m_bHasMusic = static_cast(query.getColumn(index++)) != 0; + song->m_bHasBanner = static_cast(query.getColumn(index++)) != 0; + song->m_fMusicLengthSeconds = static_cast(query.getColumn(index++)); + + Steps* pNewNotes = nullptr; + + SQLite::Statement qSteps(*db, "SELECT * FROM steps WHERE SONGID=" + to_string(songid)); + + while (qSteps.executeStep()) { + /* + db->exec("CREATE TABLE IF NOT EXISTS steps (id INTEGER PRIMARY KEY, " + "CHARTNAME TEXT, STEPSTYPE TEXT, DESCRIPTION TEXT, CHARTSTYLE TEXT, DIFFICULTY INTEGER, " + "METER INTEGER, OVERALL TEXT, STREAM TEXT, JUMPSTREAM TEXT, HANDSTREAM TEXT, " + "STAMINA TEXT, JACKSPEED TEXT, JACKSTAMINA TEXT, TECHNICAL TEXT, CHARTKEY TEXT, " + "MUSIC TEXT, RADARVALUES TEXT, CREDIT TEXT, " + "TIMINGDATAID INTEGER, DISPLAYBPMMIN FLOAT, DISPLAYBPMMAX FLOAT, STEPFILENAME TEXT, SONGID INTEGER, " + "CONSTRAINT fk_songid FOREIGN KEY (SONGID) REFERENCES songs(id), " + "CONSTRAINT fk_timingdataid FOREIGN KEY (TIMINGDATAID) REFERENCES songs(ID))"); + */ + int stepsIndex = 0; + + pNewNotes = song->CreateSteps(); + int stepsID = qSteps.getColumn(stepsIndex++); + RString chartName = static_cast(qSteps.getColumn(stepsIndex++)); + Trim(chartName); + pNewNotes->SetChartName(chartName); + string stepsType = static_cast(qSteps.getColumn(stepsIndex++)); + pNewNotes->m_StepsType = GAMEMAN->StringToStepsType(stepsType); + pNewNotes->m_StepsTypeStr = stepsType; + RString description = static_cast(qSteps.getColumn(stepsIndex++)); + Trim(description); + pNewNotes->SetDescription(description); + pNewNotes->SetChartStyle(static_cast(qSteps.getColumn(stepsIndex++))); + pNewNotes->SetDifficulty(static_cast(static_cast(qSteps.getColumn(stepsIndex++)))); + pNewNotes->SetMeter(qSteps.getColumn(stepsIndex++)); + + MinaSD o; + for (size_t i = 0; i < NUM_Skillset; i++) + o.emplace_back(SSC::msdsplit(static_cast(qSteps.getColumn(stepsIndex++)))); + pNewNotes->SetAllMSD(o); + + pNewNotes->SetChartKey(static_cast(qSteps.getColumn(stepsIndex++))); + pNewNotes->SetMusicFile(static_cast(qSteps.getColumn(stepsIndex++))); + string radarValues = static_cast(qSteps.getColumn(stepsIndex++)); + vector values; + split(radarValues, ",", values, true); + RadarValues rv; + rv.Zero(); + for (size_t i = 0; i < NUM_RadarCategory; ++i) + rv[i] = StringToInt(values[i]); + pNewNotes->SetCachedRadarValues(rv); + pNewNotes->SetCredit(static_cast(qSteps.getColumn(stepsIndex++))); + + /* If this is called, the chart does not use the same attacks + * as the Song's timing. No other changes are required. */ + if(!qSteps.isColumnNull(stepsIndex)) { + int timingID = qSteps.getColumn(stepsIndex++); + int timingIndex = 1; //Skip the first value, the id + SQLite::Statement qTiming(*db, "SELECT * FROM timingdatas WHERE ID=" + to_string(timingID)); + if (qTiming.executeStep()) { + TimingData stepsTiming = TimingData(song->m_SongTiming.m_fBeat0OffsetInSeconds); + //Load timing data + stepsTiming.m_fBeat0OffsetInSeconds = static_cast(qTiming.getColumn(timingIndex++)); + SSCLoader::ProcessBPMs(stepsTiming, static_cast(qSteps.getColumn(timingIndex++)), dir); + //steps_tag_handlers["BPMS"] = &SetStepsBPMs; + SSCLoader::ProcessStops(stepsTiming, static_cast(qTiming.getColumn(timingIndex++)), dir); + //steps_tag_handlers["STOPS"] = &SetStepsStops; + SSCLoader::ProcessDelays(stepsTiming, static_cast(qTiming.getColumn(timingIndex++)), dir); + //steps_tag_handlers["DELAYS"] = &SetStepsDelays; + SSCLoader::ProcessWarps(stepsTiming, static_cast(qTiming.getColumn(timingIndex++)), song->m_fVersion, dir); + //steps_tag_handlers["WARPS"] = &SetStepsWarps; + SSCLoader::ProcessTimeSignatures(stepsTiming, static_cast(qTiming.getColumn(timingIndex++)), dir); + //steps_tag_handlers["TIMESIGNATURES"] = &SetStepsTimeSignatures; + SSCLoader::ProcessTickcounts(stepsTiming, static_cast(qTiming.getColumn(timingIndex++)), dir); + //steps_tag_handlers["TICKCOUNTS"] = &SetStepsTickCounts; + SSCLoader::ProcessCombos(stepsTiming, static_cast(qTiming.getColumn(timingIndex++)), dir); + //steps_tag_handlers["COMBOS"] = &SetStepsCombos; + SSCLoader::ProcessSpeeds(stepsTiming, static_cast(qTiming.getColumn(timingIndex++)), dir); + //steps_tag_handlers["SPEEDS"] = &SetStepsSpeeds; + SSCLoader::ProcessScrolls(stepsTiming, static_cast(qTiming.getColumn(timingIndex++)), dir); + //steps_tag_handlers["SCROLLS"] = &SetStepsScrolls; + SSCLoader::ProcessFakes(stepsTiming, static_cast(qTiming.getColumn(timingIndex++)), dir); + //steps_tag_handlers["FAKES"] = &SetStepsFakes; + SSCLoader::ProcessLabels(stepsTiming, static_cast(qTiming.getColumn(timingIndex++)), dir); + pNewNotes->m_Timing = stepsTiming; + } + } + else + qSteps.getColumn(stepsIndex++); + + int bpmminIndex = stepsIndex++; + int bpmmaxIndex = stepsIndex++; + float BPMmin = static_cast(qSteps.getColumn(bpmminIndex)); + float BPMmax = static_cast(qSteps.getColumn(bpmmaxIndex)); + if (qSteps.isColumnNull(bpmminIndex) || qSteps.isColumnNull(bpmmaxIndex)) + { + if (qSteps.isColumnNull(bpmminIndex) && qSteps.isColumnNull(bpmmaxIndex)) + pNewNotes->SetDisplayBPM(DISPLAY_BPM_RANDOM); + else + pNewNotes->SetDisplayBPM(DISPLAY_BPM_ACTUAL); } + else + { + pNewNotes->SetDisplayBPM(DISPLAY_BPM_SPECIFIED); + pNewNotes->SetMinBPM(BPMmin); + pNewNotes->SetMaxBPM(BPMmax); + } + + //pNewNotes->SetSMNoteData(""); + pNewNotes->TidyUpData(); + pNewNotes->SetFilename(static_cast(qSteps.getColumn(stepsIndex++))); + song->AddSteps(pNewNotes); } - pNewNotes->m_Timing = stepsTiming; - pNewNotes->TidyUpData(); - song->AddSteps(pNewNotes); - } - song->m_SongTiming.m_sFile = dir; // songs still have their fallback timing. - song->m_sSongFileName = dir; - song->m_fVersion = STEPFILE_VERSION_NUMBER; - SMLoader::TidyUpData(*song, true); + song->m_SongTiming.m_sFile = dir; // songs still have their fallback timing. + song->m_sSongFileName = dir; + song->m_fVersion = STEPFILE_VERSION_NUMBER; + SMLoader::TidyUpData(*song, true); - if (song->m_sMainTitle == "" || (song->m_sMusicFile == "" && song->m_vsKeysoundFile.empty())) + if (song->m_sMainTitle == "" || (song->m_sMusicFile == "" && song->m_vsKeysoundFile.empty())) + { + LOG->Warn("Main title or music file for '%s' came up blank, forced to fall back on TidyUpData to fix title and paths. Do not use # or ; in a song title.", dir.c_str()); + // Tell TidyUpData that it's not loaded from the cache because it needs + // to hit the song folder to find the files that weren't found. -Kyz + song->TidyUpData(false, false); + } + } + catch (std::exception& e) { - LOG->Warn("Main title or music file for '%s' came up blank, forced to fall back on TidyUpData to fix title and paths. Do not use # or ; in a song title.", dir.c_str()); - // Tell TidyUpData that it's not loaded from the cache because it needs - // to hit the song folder to find the files that weren't found. -Kyz - song->TidyUpData(false, false); + LOG->Trace("Error reading song %s from cache: %s", dir.c_str(), e.what()); + ResetDB(); + return false; } } /* diff --git a/src/SongCacheIndex.h b/src/SongCacheIndex.h index 8f3cc36b5d..40c6e60018 100644 --- a/src/SongCacheIndex.h +++ b/src/SongCacheIndex.h @@ -17,9 +17,10 @@ class SongCacheIndex bool OpenDB(); void ResetDB(); void DeleteDB(); - bool DBEmpty; + void CreateDBTables(); + bool DBEmpty{true}; SQLite::Database *db; - + SQLite::Transaction *curTransaction; public: SongCacheIndex(); ~SongCacheIndex(); @@ -35,7 +36,9 @@ class SongCacheIndex int InsertStepsTimingData(TimingData timing); int InsertSteps(const Steps* pSteps, int songID); bool LoadSongFromCache(Song* song, string dir); - bool SaveSong(Song& song, string dir); + bool CacheSong(Song& song, string dir); + void StartTransaction(); + void FinishTransaction(); }; extern SongCacheIndex *SONGINDEX; // global and accessible from anywhere in our program diff --git a/src/StepMania.cpp b/src/StepMania.cpp index 5b254511a4..4753782239 100644 --- a/src/StepMania.cpp +++ b/src/StepMania.cpp @@ -1138,6 +1138,7 @@ int sm_main(int argc, char* argv[]) if( PREFSMAN->m_iSoundWriteAhead ) LOG->Info( "Sound writeahead has been overridden to %i", PREFSMAN->m_iSoundWriteAhead.Get() ); + SONGINDEX = new SongCacheIndex; SOUNDMAN = new RageSoundManager; SOUNDMAN->Init(); SOUNDMAN->SetMixVolume(); @@ -1153,7 +1154,9 @@ int sm_main(int argc, char* argv[]) // depends on SONGINDEX: SONGMAN = new SongManager; + SONGINDEX->StartTransaction(); SONGMAN->InitAll( pLoadingWindow ); // this takes a long time + SONGINDEX->FinishTransaction(); CRYPTMAN = new CryptManager; // need to do this before ProfileMan if( PREFSMAN->m_bSignProfileData ) CRYPTMAN->GenerateGlobalKeys();