Index: common/mptFileIO.cpp =================================================================== --- common/mptFileIO.cpp (revision 10963) +++ common/mptFileIO.cpp (working copy) @@ -18,7 +18,13 @@ #endif // MPT_OS_WINDOWS #endif // MODPLUG_TRACKER +#if defined(MPT_ENABLE_FILEIO) +#if MPT_COMPILER_MSVC +#include +#endif // MPT_COMPILER_MSVC +#endif // MPT_ENABLE_FILEIO + OPENMPT_NAMESPACE_BEGIN @@ -80,7 +86,62 @@ namespace mpt { + +#if MPT_COMPILER_MSVC +mpt::tstring SafeOutputFile::convert_mode(std::ios_base::openmode mode, FlushMode flushMode) +{ + mpt::tstring fopen_mode; + switch(mode & ~(std::ios_base::ate | std::ios_base::binary)) + { + case std::ios_base::in: + fopen_mode = _T("r"); + break; + case std::ios_base::out: + MPT_FALLTHROUGH; + case std::ios_base::out | std::ios_base::trunc: + fopen_mode = _T("w"); + break; + case std::ios_base::app: + MPT_FALLTHROUGH; + case std::ios_base::out | std::ios_base::app: + fopen_mode = _T("a"); + break; + case std::ios_base::out | std::ios_base::in: + fopen_mode = _T("r+"); + break; + case std::ios_base::out | std::ios_base::in | std::ios_base::trunc: + fopen_mode = _T("w+"); + break; + case std::ios_base::out | std::ios_base::in | std::ios_base::app: + MPT_FALLTHROUGH; + case std::ios_base::in | std::ios_base::app: + fopen_mode = _T("a+"); + break; + } + if(fopen_mode.empty()) + { + return fopen_mode; + } + if(mode & std::ios_base::binary) + { + fopen_mode += _T("b"); + } + if(flushMode == FlushMode::Full) + { + fopen_mode += _T("c"); // force commit on fflush (MSVC specific) + } + return fopen_mode; +} + +#endif // MPT_COMPILER_MSVC + +} // namespace mpt + + + +namespace mpt { + LazyFileRef & LazyFileRef::operator = (const std::vector &data) { mpt::ofstream file(m_Filename, std::ios::binary); Index: common/mptFileIO.h =================================================================== --- common/mptFileIO.h (revision 10963) +++ common/mptFileIO.h (working copy) @@ -23,6 +23,14 @@ #include #include +#if MPT_COMPILER_MSVC +#include +#endif // !MPT_COMPILER_MSVC + +#if MPT_COMPILER_MSVC +#include +#endif // !MPT_COMPILER_MSVC + #endif // MPT_ENABLE_FILEIO @@ -125,6 +133,14 @@ { detail::fstream_open(*this, filename, mode); } +#if MPT_COMPILER_MSVC +protected: + ofstream(FILE * file) + : std::ofstream(file) + { + } +#endif // MPT_COMPILER_MSVC +public: void open(const mpt::PathString & filename, std::ios_base::openmode mode = std::ios_base::out) { detail::fstream_open(*this, filename, mode); @@ -137,8 +153,103 @@ #endif }; +enum class FlushMode +{ + None = 0, // no explicit flushes at all + Single = 1, // explicitly flush higher-leverl API layers + Full = 2, // explicitly flush *all* layers, up to and including disk write caches +}; +static inline FlushMode FlushModeFromBool(bool flush) +{ + return flush ? FlushMode::Full : FlushMode::None; +} +class SafeOutputFile + : public mpt::ofstream +{ +private: + typedef std::ofstream Tbase; + FlushMode m_FlushMode; +#if MPT_COMPILER_MSVC + FILE *m_f; +#endif // MPT_COMPILER_MSVC +#if MPT_COMPILER_MSVC + static mpt::tstring convert_mode(std::ios_base::openmode mode, FlushMode flushMode); + FILE * internal_fopen(const mpt::PathString &filename, std::ios_base::openmode mode, FlushMode flushMode) + { + mpt::tstring fopen_mode = convert_mode(mode, flushMode); + if(fopen_mode.empty()) + { + return nullptr; + } + FILE *f = +#ifdef UNICODE + _wfopen(filename.AsNativePrefixed().c_str(), fopen_mode.c_str()) +#else + fopen(filename.AsNativePrefixed().c_str(), fopen_mode.c_str()) +#endif + ; + if(!f) + { + return nullptr; + } + if(mode & std::ios_base::ate) + { + if(fseek(f, 0, SEEK_END) != 0) + { + fclose(f); + f = nullptr; + return nullptr; + } + } + m_f = f; + return f; + } +#endif // MPT_COMPILER_MSVC +public: + SafeOutputFile() = delete; + explicit SafeOutputFile(const mpt::PathString &filename, std::ios_base::openmode mode = std::ios_base::out, FlushMode flushMode = FlushMode::Full) +#if MPT_COMPILER_MSVC + : mpt::ofstream(internal_fopen(filename, mode | std::ios_base::out, flushMode)) +#else // !MPT_COMPILER_MSVC + : mpt::ofstream(filename, mode) +#endif // MPT_COMPILER_MSVC + , m_FlushMode(flushMode) + { + } + ~SafeOutputFile() + { + if(!*this) + { + return; + } + if(!rdbuf()) + { + return; + } + #if MPT_COMPILER_MSVC + if(!m_f) + { + return; + } + #endif // MPT_COMPILER_MSVC + if(m_FlushMode != FlushMode::None) + { + rdbuf()->pubsync(); + } + #if MPT_COMPILER_MSVC + if(m_FlushMode != FlushMode::None) + { + fflush(m_f); + } + fclose(m_f); + #endif // MPT_COMPILER_MSVC + } +}; + + + // LazyFileRef is a simple reference to an on-disk file by the means of a // filename which allows easy assignment of the whole file contents to and from // byte buffers. @@ -223,5 +334,6 @@ #endif // MPT_ENABLE_FILEIO + OPENMPT_NAMESPACE_END Index: mptrack/CommandSet.cpp =================================================================== --- mptrack/CommandSet.cpp (revision 10963) +++ mptrack/CommandSet.cpp (working copy) @@ -1510,7 +1510,7 @@ ... */ - mpt::ofstream f(filename); + mpt::SafeOutputFile f(filename, std::ios::out, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(!f) { ErrorBox(IDS_CANT_OPEN_FILE_FOR_WRITING); Index: mptrack/ExceptionHandler.cpp =================================================================== --- mptrack/ExceptionHandler.cpp (revision 10963) +++ mptrack/ExceptionHandler.cpp (working copy) @@ -268,13 +268,13 @@ errorMessage += MPT_ULITERAL("\n\n"); { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("error.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("error.txt"), std::ios::binary, mpt::FlushMode::Full); f.imbue(std::locale::classic()); f << mpt::String::Replace(mpt::ToCharset(mpt::CharsetUTF8, errorMessage), "\n", "\r\n"); } { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("threads.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("threads.txt"), std::ios::binary, mpt::FlushMode::Full); f.imbue(std::locale::classic()); f << mpt::format("current : %1")(mpt::fmt::hex0<8>(GetCurrentThreadId())) << "\r\n"; f << mpt::format("GUI : %1")(mpt::fmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindGUI))) << "\r\n"; @@ -290,7 +290,7 @@ }; { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("active-settings.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("active-settings.txt"), std::ios::binary, mpt::FlushMode::Full); f.imbue(std::locale::classic()); if(&theApp.GetSettings()) { @@ -357,13 +357,13 @@ */ { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("about-openmpt.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("about-openmpt.txt"), std::ios::binary, mpt::FlushMode::Full); f.imbue(std::locale::classic()); f << mpt::ToCharset(mpt::CharsetUTF8, CAboutDlg::GetTabText(0)); } { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("about-components.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("about-components.txt"), std::ios::binary, mpt::FlushMode::Full); f.imbue(std::locale::classic()); f << mpt::ToCharset(mpt::CharsetUTF8, CAboutDlg::GetTabText(1)); } Index: mptrack/mod2midi.cpp =================================================================== --- mptrack/mod2midi.cpp (revision 10963) +++ mptrack/mod2midi.cpp (working copy) @@ -695,7 +695,7 @@ void CDoMidiConvert::Run() { - mpt::ofstream f(m_fileName, std::ios::binary); + mpt::SafeOutputFile f(m_fileName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(!f.good()) { Reporting::Error("Could not open file for writing. Is it open in another application?"); Index: mptrack/Mod2wave.cpp =================================================================== --- mptrack/Mod2wave.cpp (revision 10963) +++ mptrack/Mod2wave.cpp (working copy) @@ -949,7 +949,7 @@ normalizeFile.open(normalizeFileName, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); } - mpt::ofstream fileStream(m_lpszFileName, std::ios::binary); + mpt::SafeOutputFile fileStream(m_lpszFileName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(!fileStream) { Index: mptrack/Moddoc.cpp =================================================================== --- mptrack/Moddoc.cpp (revision 10963) +++ mptrack/Moddoc.cpp (working copy) @@ -278,7 +278,7 @@ return FALSE; BOOL ok = FALSE; - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(f) { BeginWaitCursor(); @@ -1898,7 +1898,7 @@ if(!dlg.Show()) return; filename = dlg.GetFirstFile(); - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(!f) return; Index: mptrack/Modedit.cpp =================================================================== --- mptrack/Modedit.cpp (revision 10963) +++ mptrack/Modedit.cpp (working copy) @@ -1099,7 +1099,7 @@ CStringA s; EnvelopeToString(s, pIns->GetEnvelope(nEnv)); - mpt::ofstream f(fileName, std::ios::binary); + mpt::SafeOutputFile f(fileName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(!f) { EndWaitCursor(); Index: mptrack/Settings.cpp =================================================================== --- mptrack/Settings.cpp (revision 10963) +++ mptrack/Settings.cpp (working copy) @@ -490,7 +490,7 @@ static void WriteFileUTF16LE(const mpt::PathString &filename, const std::wstring &str) { STATIC_ASSERT(sizeof(wchar_t) == 2); - mpt::ofstream inifile(filename, std::ios::binary); + mpt::SafeOutputFile inifile(filename, std::ios::binary, mpt::FlushMode::Full); const uint8 UTF16LE_BOM[] = { 0xff, 0xfe }; inifile.write(reinterpret_cast(UTF16LE_BOM), 2); inifile.write(reinterpret_cast(str.c_str()), str.length() * sizeof(std::wstring::value_type)); Index: mptrack/TrackerSettings.cpp =================================================================== --- mptrack/TrackerSettings.cpp (revision 10963) +++ mptrack/TrackerSettings.cpp (working copy) @@ -222,6 +222,7 @@ , MiscAllowMultipleCommandsPerKey(conf, MPT_USTRING("Misc"), MPT_USTRING("AllowMultipleCommandsPerKey"), false) , MiscDistinguishModifiers(conf, MPT_USTRING("Misc"), MPT_USTRING("DistinguishModifiers"), false) , MiscProcessPriorityClass(conf, MPT_USTRING("Misc"), MPT_USTRING("ProcessPriorityClass"), ProcessPriorityClassNORMAL) + , MiscFlushFileBuffersOnSave(conf, MPT_USTRING("Misc"), MPT_USTRING("FlushFileBuffersOnSave"), true) // Sound Settings , m_SoundShowDeprecatedDevices(conf, MPT_USTRING("Sound Settings"), MPT_USTRING("ShowDeprecatedDevices"), true) , m_SoundShowNotRecommendedDeviceWarning(conf, MPT_USTRING("Sound Settings"), MPT_USTRING("ShowNotRecommendedDeviceWarning"), true) @@ -786,7 +787,7 @@ if(!fn.FileOrDirectoryExists()) { CTuning * pT = CSoundFile::CreateTuning12TET("12TET"); - mpt::ofstream f(fn, std::ios::binary); + mpt::SafeOutputFile f(fn, std::ios::binary, mpt::FlushMode::Full); pT->Serialize(f); f.close(); delete pT; @@ -798,7 +799,7 @@ if(!fn.FileOrDirectoryExists()) { CTuning * pT = CSoundFile::CreateTuning12TET("12TET [[fs15 1.17.02.49]]"); - mpt::ofstream f(fn, std::ios::binary); + mpt::SafeOutputFile f(fn, std::ios::binary, mpt::FlushMode::Full); pT->Serialize(f); f.close(); delete pT; Index: mptrack/TrackerSettings.h =================================================================== --- mptrack/TrackerSettings.h (revision 10963) +++ mptrack/TrackerSettings.h (working copy) @@ -637,6 +637,7 @@ CachedSetting MiscAllowMultipleCommandsPerKey; CachedSetting MiscDistinguishModifiers; Setting MiscProcessPriorityClass; + Setting MiscFlushFileBuffersOnSave; // Sound Settings Index: mptrack/TuningDialog.cpp =================================================================== --- mptrack/TuningDialog.cpp (revision 10963) +++ mptrack/TuningDialog.cpp (working copy) @@ -617,7 +617,7 @@ BeginWaitCursor(); - mpt::ofstream fout(dlg.GetFirstFile(), std::ios::binary); + mpt::SafeOutputFile fout(dlg.GetFirstFile(), std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(tuningFilter != -1 && filterIndex == tuningFilter) { @@ -691,7 +691,7 @@ SanitizeFilename(nameW); fileNameW = mpt::String::Replace(fileNameW, MPT_USTRING("%tuning_name%"), nameW); fileName = mpt::PathString::FromUnicode(fileNameW); - mpt::ofstream fout(fileName, std::ios::binary); + mpt::SafeOutputFile fout(fileName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(tuning.Serialize(fout) != Tuning::SerializationResult::Success) { failure = true; Index: soundlib/Load_it.cpp =================================================================== --- soundlib/Load_it.cpp (revision 10963) +++ soundlib/Load_it.cpp (working copy) @@ -1364,7 +1364,6 @@ bool CSoundFile::SaveIT(std::ostream &f, const mpt::PathString &filename, bool compatibilityExport) { - if(!f) return false; const CModSpecifications &specs = (GetType() == MOD_TYPE_MPT ? ModSpecs::mptm : (compatibilityExport ? ModSpecs::it : ModSpecs::itEx)); Index: soundlib/Load_mod.cpp =================================================================== --- soundlib/Load_mod.cpp (revision 10963) +++ soundlib/Load_mod.cpp (working copy) @@ -2184,7 +2184,10 @@ bool CSoundFile::SaveMod(std::ostream &f) const { - if(!f || m_nChannels == 0) return false; + if(m_nChannels == 0) + { + return false; + } // Write song title { Index: soundlib/Load_s3m.cpp =================================================================== --- soundlib/Load_s3m.cpp (revision 10963) +++ soundlib/Load_s3m.cpp (working copy) @@ -610,7 +610,10 @@ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, }; - if(!f || m_nChannels == 0) return false; + if(m_nChannels == 0) + { + return false; + } const bool saveMuteStatus = #ifdef MODPLUG_TRACKER Index: soundlib/Load_xm.cpp =================================================================== --- soundlib/Load_xm.cpp (revision 10963) +++ soundlib/Load_xm.cpp (working copy) @@ -749,7 +749,6 @@ bool CSoundFile::SaveXM(std::ostream &f, bool compatibilityExport) { - if(!f) return false; bool addChannel = false; // avoid odd channel count for FT2 compatibility Index: soundlib/plugins/PlugInterface.cpp =================================================================== --- soundlib/plugins/PlugInterface.cpp (revision 10963) +++ soundlib/plugins/PlugInterface.cpp (working copy) @@ -652,7 +652,7 @@ bool bank = (dlg.GetExtension() == MPT_PATHSTRING("fxb")); - mpt::ofstream f(dlg.GetFirstFile(), std::ios::binary); + mpt::SafeOutputFile f(dlg.GetFirstFile(), std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(f.good() && VSTPresets::SaveFile(f, *this, bank)) { return true; Index: soundlib/SampleFormatFLAC.cpp =================================================================== --- soundlib/SampleFormatFLAC.cpp (revision 10963) +++ soundlib/SampleFormatFLAC.cpp (working copy) @@ -533,7 +533,15 @@ { #ifdef MPT_WITH_FLAC - mpt::ofstream f(filename, std::ios::binary); + const mpt::FlushMode flushMode = + #ifdef MODPLUG_TRACKER + mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave) + #else + mpt::FlushMode::Full + #endif + ; + + mpt::SafeOutputFile f(filename, std::ios::binary, flushMode); if(!f) { return false; Index: soundlib/SampleFormats.cpp =================================================================== --- soundlib/SampleFormats.cpp (revision 10963) +++ soundlib/SampleFormats.cpp (working copy) @@ -39,6 +39,22 @@ OPENMPT_NAMESPACE_BEGIN +#ifndef MODPLUG_NO_FILESAVE + +static mpt::FlushMode GetSampleFileFlushMode() +{ + return + #ifdef MODPLUG_TRACKER + mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave) + #else + mpt::FlushMode::Full + #endif + ; +} + +#endif // !MODPLUG_NO_FILESAVE + + bool CSoundFile::ReadSampleFromFile(SAMPLEINDEX nSample, FileReader &file, bool mayNormalize, bool includeInstrumentFormats) { if(!nSample || nSample >= MAX_SAMPLES) return false; @@ -539,7 +555,7 @@ #ifndef MODPLUG_NO_FILESAVE bool CSoundFile::SaveWAVSample(SAMPLEINDEX nSample, const mpt::PathString &filename) const { - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary, GetSampleFileFlushMode()); if(!f) { return false; @@ -829,7 +845,7 @@ bool CSoundFile::SaveRAWSample(SAMPLEINDEX nSample, const mpt::PathString &filename) const { - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary, GetSampleFileFlushMode()); if(!f) { return false; @@ -1170,7 +1186,7 @@ bool CSoundFile::SaveS3ISample(SAMPLEINDEX smp, const mpt::PathString &filename) const { - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary, GetSampleFileFlushMode()); if(!f) return false; @@ -1342,7 +1358,7 @@ return false; } - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary, GetSampleFileFlushMode()); if(!f) { return false; @@ -2160,7 +2176,7 @@ { return false; } - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary, GetSampleFileFlushMode()); if(!f.good()) { return false; @@ -3255,7 +3271,7 @@ ModInstrument *pIns = Instruments[nInstr]; if((!pIns) || filename.empty()) return false; - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary, GetSampleFileFlushMode()); if(!f) { return false; Index: soundlib/tuningCollection.cpp =================================================================== --- soundlib/tuningCollection.cpp (revision 10963) +++ soundlib/tuningCollection.cpp (working copy) @@ -15,6 +15,9 @@ #include #include "../common/mptFileIO.h" #include "Loaders.h" +#ifdef MODPLUG_TRACKER +#include "../mptrack/TrackerSettings.h" +#endif //MODPLUG_TRACKER OPENMPT_NAMESPACE_BEGIN @@ -281,7 +284,7 @@ error = true; } else { - mpt::ofstream fout(fn, std::ios::binary); + mpt::SafeOutputFile fout(fn, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(tuning.Serialize(fout) != Tuning::SerializationResult::Success) { error = true;