mirror of
https://github.com/cookiengineer/audacity
synced 2025-08-03 01:19:24 +02:00
Strong exception safety in all uses of XMLFileWriter...
... Strong, meaning that the file at the specified path is created or modified only if all write operations complete without exceptions, barring one very unlikely possibility that a final file rename fails, but even in that case the output is successfully written to some path. This commit does not add throws, but changes the type thrown to a subclass of AudacityException, so that GuardedCall will cause the user to see an error dialog in all cases. Duplicated logic for making temporary files and backups is now all in one place, the class XMLWriter. There may be more new GuardedCalls than necessary -- the catch-all for the event loop, AudacityApp::OnExceptionInMainLoop, might be trusted instead in some cases -- but they are sufficient.
This commit is contained in:
parent
b81cdee7e3
commit
3bb04245c5
@ -738,14 +738,10 @@ bool AutoSaveFile::Decode(const wxString & fileName)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
XMLFileWriter out;
|
|
||||||
wxString tempName;
|
|
||||||
len = file.Length() - len;
|
len = file.Length() - len;
|
||||||
{
|
|
||||||
using Chars = ArrayOf < char >;
|
using Chars = ArrayOf < char >;
|
||||||
using WxChars = ArrayOf < wxChar >;
|
using WxChars = ArrayOf < wxChar >;
|
||||||
Chars buf{ len };
|
Chars buf{ len };
|
||||||
|
|
||||||
if (file.Read(buf.get(), len) != len)
|
if (file.Read(buf.get(), len) != len)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@ -755,32 +751,20 @@ bool AutoSaveFile::Decode(const wxString & fileName)
|
|||||||
|
|
||||||
file.Close();
|
file.Close();
|
||||||
|
|
||||||
// Decode to a temporary file to preserve the original.
|
|
||||||
tempName = fn.CreateTempFileName(fnPath);
|
|
||||||
bool opened = false;
|
|
||||||
|
|
||||||
// JKC: ANSWER-ME: Is the try catch actually doing anything?
|
// JKC: ANSWER-ME: Is the try catch actually doing anything?
|
||||||
// If it is useful, why are we not using it everywhere?
|
// If it is useful, why are we not using it everywhere?
|
||||||
// If it isn't useful, why are we doing it here?
|
// If it isn't useful, why are we doing it here?
|
||||||
try
|
// PRL: Yes, now we are doing GuardedCall everywhere that XMLFileWriter is
|
||||||
{
|
// used.
|
||||||
out.Open(tempName, wxT("wb"));
|
return GuardedCall< bool >( [&] {
|
||||||
opened = out.IsOpened();
|
XMLFileWriter out{ fileName, _("Error Decoding File") };
|
||||||
}
|
|
||||||
catch (const XMLFileWriterException&)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!opened)
|
IdMap mIds;
|
||||||
{
|
IdMapArray mIdStack;
|
||||||
wxRemoveFile(tempName);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
mIds.clear();
|
mIds.clear();
|
||||||
|
|
||||||
while (!in.Eof() && !out.Error())
|
while ( !in.Eof() )
|
||||||
{
|
{
|
||||||
short id;
|
short id;
|
||||||
|
|
||||||
@ -952,29 +936,9 @@ bool AutoSaveFile::Decode(const wxString & fileName)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
bool error = out.Error();
|
out.Commit();
|
||||||
|
|
||||||
out.Close();
|
|
||||||
|
|
||||||
// Bail if decoding failed.
|
|
||||||
if (error)
|
|
||||||
{
|
|
||||||
// File successfully decoded
|
|
||||||
wxRemoveFile(tempName);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decoding was successful, so remove the original file and replace with decoded one.
|
|
||||||
if (wxRemoveFile(fileName))
|
|
||||||
{
|
|
||||||
if (!wxRenameFile(tempName, fileName))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
@ -115,8 +115,6 @@ private:
|
|||||||
wxMemoryOutputStream mBuffer;
|
wxMemoryOutputStream mBuffer;
|
||||||
wxMemoryOutputStream mDict;
|
wxMemoryOutputStream mDict;
|
||||||
NameMap mNames;
|
NameMap mNames;
|
||||||
IdMap mIds;
|
|
||||||
IdMapArray mIdStack;
|
|
||||||
size_t mAllocSize;
|
size_t mAllocSize;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -44,44 +44,6 @@ On failure the old version is put back in place.
|
|||||||
#include "Legacy.h"
|
#include "Legacy.h"
|
||||||
#include "xml/XMLWriter.h"
|
#include "xml/XMLWriter.h"
|
||||||
|
|
||||||
class AutoRollbackRenamer {
|
|
||||||
public:
|
|
||||||
AutoRollbackRenamer(wxString oldName, wxString newName) {
|
|
||||||
mOldName = oldName;
|
|
||||||
mNewName = newName;
|
|
||||||
mRenameSucceeded = ::wxRenameFile(mOldName, mNewName);
|
|
||||||
mFinished = false;
|
|
||||||
mNewFile = NULL;
|
|
||||||
}
|
|
||||||
~AutoRollbackRenamer()
|
|
||||||
{
|
|
||||||
if (mNewFile)
|
|
||||||
fclose(mNewFile);
|
|
||||||
|
|
||||||
if (mRenameSucceeded && !mFinished) {
|
|
||||||
::wxRemoveFile(mOldName);
|
|
||||||
::wxRenameFile(mNewName, mOldName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool RenameSucceeded()
|
|
||||||
{
|
|
||||||
return mRenameSucceeded;
|
|
||||||
}
|
|
||||||
void Finished()
|
|
||||||
{
|
|
||||||
mFinished = true;
|
|
||||||
}
|
|
||||||
void SetNewFile(FILE *f)
|
|
||||||
{
|
|
||||||
mNewFile = f;
|
|
||||||
}
|
|
||||||
|
|
||||||
wxString mOldName, mNewName;
|
|
||||||
bool mRenameSucceeded;
|
|
||||||
bool mFinished;
|
|
||||||
FILE *mNewFile;
|
|
||||||
};
|
|
||||||
|
|
||||||
static bool ConvertLegacyTrack(wxTextFile *f, XMLFileWriter &xmlFile)
|
static bool ConvertLegacyTrack(wxTextFile *f, XMLFileWriter &xmlFile)
|
||||||
// may throw
|
// may throw
|
||||||
{
|
{
|
||||||
@ -291,42 +253,15 @@ static bool ConvertLegacyTrack(wxTextFile *f, XMLFileWriter &xmlFile)
|
|||||||
bool ConvertLegacyProjectFile(const wxFileName &filename)
|
bool ConvertLegacyProjectFile(const wxFileName &filename)
|
||||||
{
|
{
|
||||||
wxTextFile f;
|
wxTextFile f;
|
||||||
XMLFileWriter xmlFile;
|
|
||||||
int index = 0;
|
|
||||||
wxString backupName;
|
|
||||||
|
|
||||||
do {
|
const wxString name = filename.GetFullPath();
|
||||||
index++;
|
f.Open( name );
|
||||||
fflush(stdout);
|
|
||||||
backupName = filename.GetPath() + wxFILE_SEP_PATH + filename.GetName() +
|
|
||||||
wxT("_bak") + wxString::Format(wxT("%d"), index) + wxT(".") + filename.GetExt();
|
|
||||||
} while(::wxFileExists(backupName));
|
|
||||||
|
|
||||||
// This will move the original file out of the way, but
|
|
||||||
// move it back if we exit from this function early.
|
|
||||||
AutoRollbackRenamer renamer(filename.GetFullPath(), backupName);
|
|
||||||
if (!renamer.RenameSucceeded())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
f.Open(backupName);
|
|
||||||
if (!f.IsOpened())
|
if (!f.IsOpened())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
wxString name = filename.GetFullPath();
|
return GuardedCall< bool >( [&] {
|
||||||
|
XMLFileWriter xmlFile{ name, _("Error Converting Legacy Project File") };
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
xmlFile.Open(name, wxT("wb"));
|
|
||||||
}
|
|
||||||
catch (const XMLFileWriterException&)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
renamer.SetNewFile(xmlFile.fp());
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
xmlFile.Write(wxT("<?xml version=\"1.0\"?>\n"));
|
xmlFile.Write(wxT("<?xml version=\"1.0\"?>\n"));
|
||||||
|
|
||||||
wxString label;
|
wxString label;
|
||||||
@ -360,19 +295,15 @@ bool ConvertLegacyProjectFile(const wxFileName &filename)
|
|||||||
label = f.GetNextLine();
|
label = f.GetNextLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close original before Commit() tries to overwrite it.
|
||||||
|
f.Close();
|
||||||
|
|
||||||
xmlFile.EndTag(wxT("audacityproject"));
|
xmlFile.EndTag(wxT("audacityproject"));
|
||||||
xmlFile.Close();
|
xmlFile.Commit();
|
||||||
}
|
|
||||||
catch (const XMLFileWriterException&)
|
|
||||||
{
|
|
||||||
// Error writing XML file (e.g. disk full)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
renamer.Finished();
|
::wxMessageBox(wxString::Format(_("Converted a 1.0 project file to the new format.\nThe old file has been saved as '%s'"), xmlFile.GetBackupName().c_str()),
|
||||||
|
|
||||||
::wxMessageBox(wxString::Format(_("Converted a 1.0 project file to the new format.\nThe old file has been saved as '%s'"), backupName.c_str()),
|
|
||||||
_("Opening Audacity Project"));
|
_("Opening Audacity Project"));
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
@ -3803,37 +3803,21 @@ bool AudacityProject::Save(bool overwrite /* = true */ ,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto success = GuardedCall< bool >( [&] {
|
||||||
// Write the AUP file.
|
// Write the AUP file.
|
||||||
XMLFileWriter saveFile;
|
XMLFileWriter saveFile{ mFileName, _("Error Saving Project") };
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
saveFile.Open(mFileName, wxT("wb"));
|
|
||||||
|
|
||||||
WriteXMLHeader(saveFile);
|
WriteXMLHeader(saveFile);
|
||||||
WriteXML(saveFile);
|
WriteXML(saveFile);
|
||||||
mStrOtherNamesArray.Clear();
|
mStrOtherNamesArray.Clear();
|
||||||
|
|
||||||
saveFile.Close();
|
saveFile.Commit();
|
||||||
}
|
|
||||||
catch (const XMLFileWriterException &exception)
|
|
||||||
{
|
|
||||||
wxMessageBox(wxString::Format(
|
|
||||||
_("Couldn't write to file \"%s\": %s"),
|
|
||||||
mFileName.c_str(), exception.GetMessage().c_str()),
|
|
||||||
_("Error Saving Project"), wxICON_ERROR);
|
|
||||||
|
|
||||||
// When XMLWriter throws an exception, it tries to close it before,
|
return true;
|
||||||
// so we can at least try to DELETE the incomplete file and move the
|
} );
|
||||||
// backup file over.
|
|
||||||
if (safetyFileName != wxT(""))
|
|
||||||
{
|
|
||||||
wxRemove(mFileName);
|
|
||||||
wxRename(safetyFileName, mFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!success)
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
if (bWantSaveCompressed)
|
if (bWantSaveCompressed)
|
||||||
mWantSaveCompressed = false; // Don't want this mode for AudacityProject::WriteXML() any more.
|
mWantSaveCompressed = false; // Don't want this mode for AudacityProject::WriteXML() any more.
|
||||||
@ -5103,7 +5087,9 @@ void AudacityProject::AutoSave()
|
|||||||
wxString fn = wxFileName(FileNames::AutoSaveDir(),
|
wxString fn = wxFileName(FileNames::AutoSaveDir(),
|
||||||
projName + wxString(wxT(" - ")) + CreateUniqueName()).GetFullPath();
|
projName + wxString(wxT(" - ")) + CreateUniqueName()).GetFullPath();
|
||||||
|
|
||||||
try
|
// PRL: I found a try-catch and rewrote it,
|
||||||
|
// but this guard is unnecessary because AutoSaveFile does not throw
|
||||||
|
bool success = GuardedCall< bool >( [&]
|
||||||
{
|
{
|
||||||
VarSetter<bool> setter(&mAutoSaving, true, false);
|
VarSetter<bool> setter(&mAutoSaving, true, false);
|
||||||
|
|
||||||
@ -5114,18 +5100,11 @@ void AudacityProject::AutoSave()
|
|||||||
|
|
||||||
wxFFile saveFile;
|
wxFFile saveFile;
|
||||||
saveFile.Open(fn + wxT(".tmp"), wxT("wb"));
|
saveFile.Open(fn + wxT(".tmp"), wxT("wb"));
|
||||||
buffer.Write(saveFile);
|
return buffer.Write(saveFile);
|
||||||
saveFile.Close();
|
} );
|
||||||
}
|
|
||||||
catch (const XMLFileWriterException &exception)
|
|
||||||
{
|
|
||||||
wxMessageBox(wxString::Format(
|
|
||||||
_("Couldn't write to file \"%s\": %s"),
|
|
||||||
(fn + wxT(".tmp")).c_str(), exception.GetMessage().c_str()),
|
|
||||||
_("Error Writing Autosave File"), wxICON_ERROR, this);
|
|
||||||
|
|
||||||
|
if (!success)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
// Now that we have a NEW auto-save file, DELETE the old one
|
// Now that we have a NEW auto-save file, DELETE the old one
|
||||||
DeleteCurrentAutoSaveFile();
|
DeleteCurrentAutoSaveFile();
|
||||||
|
27
src/Tags.cpp
27
src/Tags.cpp
@ -1219,12 +1219,9 @@ void TagsEditor::OnSave(wxCommandEvent & WXUNUSED(event))
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GuardedCall< void >( [&] {
|
||||||
// Create/Open the file
|
// Create/Open the file
|
||||||
XMLFileWriter writer;
|
XMLFileWriter writer{ fn, _("Error Saving Tags File") };
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
writer.Open(fn, wxT("wb"));
|
|
||||||
|
|
||||||
// Remember title and track in case they're read only
|
// Remember title and track in case they're read only
|
||||||
wxString title = mLocal.GetTag(TAG_TITLE);
|
wxString title = mLocal.GetTag(TAG_TITLE);
|
||||||
@ -1240,9 +1237,7 @@ void TagsEditor::OnSave(wxCommandEvent & WXUNUSED(event))
|
|||||||
mLocal.SetTag(TAG_TRACK, wxEmptyString);
|
mLocal.SetTag(TAG_TRACK, wxEmptyString);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the metadata
|
auto cleanup = finally( [&] {
|
||||||
mLocal.WriteXML(writer);
|
|
||||||
|
|
||||||
// Restore title
|
// Restore title
|
||||||
if (!mEditTitle) {
|
if (!mEditTitle) {
|
||||||
mLocal.SetTag(TAG_TITLE, title);
|
mLocal.SetTag(TAG_TITLE, title);
|
||||||
@ -1252,17 +1247,13 @@ void TagsEditor::OnSave(wxCommandEvent & WXUNUSED(event))
|
|||||||
if (!mEditTrack) {
|
if (!mEditTrack) {
|
||||||
mLocal.SetTag(TAG_TRACK, track);
|
mLocal.SetTag(TAG_TRACK, track);
|
||||||
}
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
// Close the file
|
// Write the metadata
|
||||||
writer.Close();
|
mLocal.WriteXML(writer);
|
||||||
}
|
|
||||||
catch (const XMLFileWriterException &exception)
|
writer.Commit();
|
||||||
{
|
} );
|
||||||
wxMessageBox(wxString::Format(
|
|
||||||
_("Couldn't write to file \"%s\": %s"),
|
|
||||||
fn.c_str(), exception.GetMessage().c_str()),
|
|
||||||
_("Error Saving Tags File"), wxICON_ERROR, this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagsEditor::OnSaveDefaults(wxCommandEvent & WXUNUSED(event))
|
void TagsEditor::OnSaveDefaults(wxCommandEvent & WXUNUSED(event))
|
||||||
|
@ -3580,6 +3580,7 @@ void EffectUIHost::OnImport(wxCommandEvent & WXUNUSED(evt))
|
|||||||
void EffectUIHost::OnExport(wxCommandEvent & WXUNUSED(evt))
|
void EffectUIHost::OnExport(wxCommandEvent & WXUNUSED(evt))
|
||||||
{
|
{
|
||||||
// may throw
|
// may throw
|
||||||
|
// exceptions are handled in AudacityApp::OnExceptionInMainLoop
|
||||||
mClient->ExportPresets();
|
mClient->ExportPresets();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
@ -1601,27 +1601,16 @@ void EffectEqualization::SaveCurves(const wxString &fileName)
|
|||||||
else
|
else
|
||||||
fn = fileName;
|
fn = fileName;
|
||||||
|
|
||||||
|
GuardedCall< void >( [&] {
|
||||||
// Create/Open the file
|
// Create/Open the file
|
||||||
XMLFileWriter eqFile;
|
|
||||||
const wxString fullPath{ fn.GetFullPath() };
|
const wxString fullPath{ fn.GetFullPath() };
|
||||||
|
XMLFileWriter eqFile{ fullPath, _("Error Saving Equalization Curves") };
|
||||||
try
|
|
||||||
{
|
|
||||||
eqFile.Open( fullPath, wxT("wb") );
|
|
||||||
|
|
||||||
// Write the curves
|
// Write the curves
|
||||||
WriteXML( eqFile );
|
WriteXML( eqFile );
|
||||||
|
|
||||||
// Close the file
|
eqFile.Commit();
|
||||||
eqFile.Close();
|
} );
|
||||||
}
|
|
||||||
catch (const XMLFileWriterException &exception)
|
|
||||||
{
|
|
||||||
wxMessageBox(wxString::Format(
|
|
||||||
_("Couldn't write to file \"%s\": %s"),
|
|
||||||
fullPath.c_str(), exception.GetMessage().c_str()),
|
|
||||||
_("Error Saving Equalization Curves"), wxICON_ERROR, mUIParent);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -3562,10 +3562,7 @@ void VSTEffect::SaveFXProgram(wxMemoryBuffer & buf, int index)
|
|||||||
void VSTEffect::SaveXML(const wxFileName & fn)
|
void VSTEffect::SaveXML(const wxFileName & fn)
|
||||||
// may throw
|
// may throw
|
||||||
{
|
{
|
||||||
XMLFileWriter xmlFile;
|
XMLFileWriter xmlFile{ fn.GetFullPath(), _("Error Saving Effect Presets") };
|
||||||
|
|
||||||
// Create/Open the file
|
|
||||||
xmlFile.Open(fn.GetFullPath(), wxT("wb"));
|
|
||||||
|
|
||||||
xmlFile.StartTag(wxT("vstprogrampersistence"));
|
xmlFile.StartTag(wxT("vstprogrampersistence"));
|
||||||
xmlFile.WriteAttr(wxT("version"), wxT("2"));
|
xmlFile.WriteAttr(wxT("version"), wxT("2"));
|
||||||
@ -3616,10 +3613,7 @@ void VSTEffect::SaveXML(const wxFileName & fn)
|
|||||||
|
|
||||||
xmlFile.EndTag(wxT("vstprogrampersistence"));
|
xmlFile.EndTag(wxT("vstprogrampersistence"));
|
||||||
|
|
||||||
// Close the file
|
xmlFile.Commit();
|
||||||
xmlFile.Close();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VSTEffect::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
|
bool VSTEffect::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
|
||||||
|
@ -491,12 +491,15 @@ FFmpegPresets::FFmpegPresets()
|
|||||||
|
|
||||||
FFmpegPresets::~FFmpegPresets()
|
FFmpegPresets::~FFmpegPresets()
|
||||||
{
|
{
|
||||||
XMLFileWriter writer;
|
// We're in a destructor! Don't let exceptions out!
|
||||||
// FIXME: TRAP_ERR Catch XMLFileWriterException
|
GuardedCall< void >( [&] {
|
||||||
wxFileName xmlFileName(FileNames::DataDir(), wxT("ffmpeg_presets.xml"));
|
wxFileName xmlFileName{ FileNames::DataDir(), wxT("ffmpeg_presets.xml") };
|
||||||
writer.Open(xmlFileName.GetFullPath(),wxT("wb"));
|
XMLFileWriter writer{
|
||||||
|
xmlFileName.GetFullPath(), _("Error Saving FFmpeg Presets") };
|
||||||
WriteXMLHeader(writer);
|
WriteXMLHeader(writer);
|
||||||
WriteXML(writer);
|
WriteXML(writer);
|
||||||
|
writer.Commit();
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
void FFmpegPresets::ImportPresets(wxString &filename)
|
void FFmpegPresets::ImportPresets(wxString &filename)
|
||||||
@ -515,11 +518,12 @@ void FFmpegPresets::ImportPresets(wxString &filename)
|
|||||||
|
|
||||||
void FFmpegPresets::ExportPresets(wxString &filename)
|
void FFmpegPresets::ExportPresets(wxString &filename)
|
||||||
{
|
{
|
||||||
XMLFileWriter writer;
|
GuardedCall< void >( [&] {
|
||||||
// FIXME: TRAP_ERR Catch XMLFileWriterException
|
XMLFileWriter writer{ filename, _("Error Saving FFmpeg Presets") };
|
||||||
writer.Open(filename,wxT("wb"));
|
|
||||||
WriteXMLHeader(writer);
|
WriteXMLHeader(writer);
|
||||||
WriteXML(writer);
|
WriteXML(writer);
|
||||||
|
writer.Commit();
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
void FFmpegPresets::GetPresetList(wxArrayString &list)
|
void FFmpegPresets::GetPresetList(wxArrayString &list)
|
||||||
|
@ -376,20 +376,11 @@ void KeyConfigPrefs::OnExport(wxCommandEvent & WXUNUSED(event))
|
|||||||
gPrefs->Write(wxT("/DefaultExportPath"), path);
|
gPrefs->Write(wxT("/DefaultExportPath"), path);
|
||||||
gPrefs->Flush();
|
gPrefs->Flush();
|
||||||
|
|
||||||
XMLFileWriter prefFile;
|
GuardedCall< void >( [&] {
|
||||||
|
XMLFileWriter prefFile{ file, _("Error Exporting Keyboard Shortcuts") };
|
||||||
try
|
|
||||||
{
|
|
||||||
prefFile.Open(file, wxT("wb"));
|
|
||||||
mManager->WriteXML(prefFile);
|
mManager->WriteXML(prefFile);
|
||||||
prefFile.Close();
|
prefFile.Commit();
|
||||||
}
|
} );
|
||||||
catch (const XMLFileWriterException &)
|
|
||||||
{
|
|
||||||
wxMessageBox(_("Couldn't write to file: ") + file,
|
|
||||||
_("Error Exporting Keyboard Shortcuts"),
|
|
||||||
wxOK | wxCENTRE, this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeyConfigPrefs::OnDefaults(wxCommandEvent & WXUNUSED(event))
|
void KeyConfigPrefs::OnDefaults(wxCommandEvent & WXUNUSED(event))
|
||||||
@ -957,20 +948,11 @@ void KeyConfigPrefs::OnExport(wxCommandEvent & WXUNUSED(event))
|
|||||||
gPrefs->Write(wxT("/DefaultExportPath"), path);
|
gPrefs->Write(wxT("/DefaultExportPath"), path);
|
||||||
gPrefs->Flush();
|
gPrefs->Flush();
|
||||||
|
|
||||||
XMLFileWriter prefFile;
|
GuardedCall< void >( [&] {
|
||||||
|
XMLFileWriter prefFile{ file, _("Error Exporting Keyboard Shortcuts") };
|
||||||
try
|
|
||||||
{
|
|
||||||
prefFile.Open(file, wxT("wb"));
|
|
||||||
mManager->WriteXML(prefFile);
|
mManager->WriteXML(prefFile);
|
||||||
prefFile.Close();
|
prefFile.Commit();
|
||||||
}
|
} );
|
||||||
catch (const XMLFileWriterException &)
|
|
||||||
{
|
|
||||||
wxMessageBox(_("Couldn't write to file: ") + file,
|
|
||||||
_("Error Exporting Keyboard Shortcuts"),
|
|
||||||
wxOK | wxCENTRE, this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeyConfigPrefs::OnDefaults(wxCommandEvent & WXUNUSED(event))
|
void KeyConfigPrefs::OnDefaults(wxCommandEvent & WXUNUSED(event))
|
||||||
|
@ -265,58 +265,107 @@ wxString XMLWriter::XMLEsc(const wxString & s)
|
|||||||
///
|
///
|
||||||
/// XMLFileWriter class
|
/// XMLFileWriter class
|
||||||
///
|
///
|
||||||
XMLFileWriter::XMLFileWriter()
|
XMLFileWriter::XMLFileWriter
|
||||||
|
( const wxString &outputPath, const wxString &caption, bool keepBackup )
|
||||||
|
: mOutputPath{ outputPath }
|
||||||
|
, mCaption{ caption }
|
||||||
|
, mKeepBackup{ keepBackup }
|
||||||
|
// may throw
|
||||||
{
|
{
|
||||||
}
|
auto tempPath = wxFileName::CreateTempFileName( outputPath );
|
||||||
|
if (!wxFFile::Open(tempPath, wxT("wb")) || !IsOpened())
|
||||||
|
ThrowException( tempPath, mCaption );
|
||||||
|
|
||||||
XMLFileWriter::~XMLFileWriter()
|
if (mKeepBackup) {
|
||||||
{
|
int index = 0;
|
||||||
if (IsOpened()) {
|
wxString backupName;
|
||||||
Close();
|
|
||||||
|
do {
|
||||||
|
wxFileName outputFn{ mOutputPath };
|
||||||
|
index++;
|
||||||
|
mBackupName =
|
||||||
|
outputFn.GetPath() + wxFILE_SEP_PATH +
|
||||||
|
outputFn.GetName() + wxT("_bak") +
|
||||||
|
wxString::Format(wxT("%d"), index) + wxT(".") +
|
||||||
|
outputFn.GetExt();
|
||||||
|
} while( ::wxFileExists( mBackupName ) );
|
||||||
|
|
||||||
|
// Open the backup file to be sure we can write it and reserve it
|
||||||
|
// until committing
|
||||||
|
if (! mBackupFile.Open( mBackupName, "wb" ) || ! mBackupFile.IsOpened() )
|
||||||
|
ThrowException( mBackupName, mCaption );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void XMLFileWriter::Open(const wxString &name, const wxString &mode)
|
|
||||||
|
XMLFileWriter::~XMLFileWriter()
|
||||||
{
|
{
|
||||||
if (!wxFFile::Open(name, mode))
|
// Don't let a destructor throw!
|
||||||
throw XMLFileWriterException(_("Error Opening File"));
|
GuardedCall< void >( [&] {
|
||||||
|
if (IsOpened()) {
|
||||||
|
// Was not committed
|
||||||
|
auto fileName = GetName();
|
||||||
|
CloseWithoutEndingTags();
|
||||||
|
::wxRemoveFile( fileName );
|
||||||
|
}
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
void XMLFileWriter::Close()
|
void XMLFileWriter::Commit()
|
||||||
// may throw
|
// may throw
|
||||||
{
|
{
|
||||||
while (mTagstack.GetCount()) {
|
while (mTagstack.GetCount()) {
|
||||||
EndTag(mTagstack[0]);
|
EndTag(mTagstack[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto tempPath = GetName();
|
||||||
CloseWithoutEndingTags();
|
CloseWithoutEndingTags();
|
||||||
|
|
||||||
|
if (mKeepBackup) {
|
||||||
|
if (! mBackupFile.Close() ||
|
||||||
|
! wxRenameFile( mOutputPath, mBackupName ) )
|
||||||
|
ThrowException( mBackupName, mCaption );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ( ! wxRemoveFile( mOutputPath ) )
|
||||||
|
ThrowException( mOutputPath, mCaption );
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we have vacated the file at the output path and are committed.
|
||||||
|
// But not completely finished with steps of the commit operation.
|
||||||
|
// If this step fails, we haven't lost the successfully written data,
|
||||||
|
// but just failed to put it in the right place.
|
||||||
|
if (! wxRenameFile( tempPath, mOutputPath ) )
|
||||||
|
throw FileException{
|
||||||
|
FileException::Cause::Rename, tempPath, mCaption, mOutputPath
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
void XMLFileWriter::CloseWithoutEndingTags()
|
void XMLFileWriter::CloseWithoutEndingTags()
|
||||||
|
// may throw
|
||||||
{
|
{
|
||||||
// Before closing, we first flush it, because if Flush() fails because of a
|
// Before closing, we first flush it, because if Flush() fails because of a
|
||||||
// "disk full" condition, we can still at least try to close the file.
|
// "disk full" condition, we can still at least try to close the file.
|
||||||
if (!wxFFile::Flush())
|
if (!wxFFile::Flush())
|
||||||
{
|
{
|
||||||
wxFFile::Close();
|
wxFFile::Close();
|
||||||
/* i18n-hint: 'flushing' means writing any remaining queued up changes
|
ThrowException( GetName(), mCaption );
|
||||||
* to disk that have not yet been written.*/
|
|
||||||
throw XMLFileWriterException(_("Error Flushing File"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note that this should never fail if flushing worked.
|
// Note that this should never fail if flushing worked.
|
||||||
if (!wxFFile::Close())
|
if (!wxFFile::Close())
|
||||||
throw XMLFileWriterException(_("Error Closing File"));
|
ThrowException( GetName(), mCaption );
|
||||||
}
|
}
|
||||||
|
|
||||||
void XMLFileWriter::Write(const wxString &data)
|
void XMLFileWriter::Write(const wxString &data)
|
||||||
|
// may throw
|
||||||
{
|
{
|
||||||
if (!wxFFile::Write(data, wxConvUTF8))
|
if (!wxFFile::Write(data, wxConvUTF8) || Error())
|
||||||
{
|
{
|
||||||
// When writing fails, we try to close the file before throwing the
|
// When writing fails, we try to close the file before throwing the
|
||||||
// exception, so it can at least be deleted.
|
// exception, so it can at least be deleted.
|
||||||
wxFFile::Close();
|
wxFFile::Close();
|
||||||
throw XMLFileWriterException(_("Error Writing to File"));
|
ThrowException( GetName(), mCaption );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,8 @@
|
|||||||
#include <wx/dynarray.h>
|
#include <wx/dynarray.h>
|
||||||
#include <wx/ffile.h>
|
#include <wx/ffile.h>
|
||||||
|
|
||||||
|
#include "../FileException.h"
|
||||||
|
|
||||||
///
|
///
|
||||||
/// XMLWriter
|
/// XMLWriter
|
||||||
///
|
///
|
||||||
@ -60,41 +62,55 @@ class AUDACITY_DLL_API XMLWriter /* not final */ {
|
|||||||
///
|
///
|
||||||
/// XMLFileWriter
|
/// XMLFileWriter
|
||||||
///
|
///
|
||||||
class AUDACITY_DLL_API XMLFileWriter final : public wxFFile, public XMLWriter {
|
|
||||||
|
/// This writes to a provisional file, and replaces the previously existing
|
||||||
|
/// contents by a file rename in Commit() only after all writes succeed.
|
||||||
|
/// The original contents may also be retained at a backup path name, as
|
||||||
|
/// directed by the optional constructor argument.
|
||||||
|
/// If it is destroyed before Commit(), then the provisional file is removed.
|
||||||
|
/// If the construction and all operations are inside a GuardedCall or event
|
||||||
|
/// handler, then the default delayed handler action in case of exceptions will
|
||||||
|
/// notify the user of problems.
|
||||||
|
class AUDACITY_DLL_API XMLFileWriter final : private wxFFile, public XMLWriter {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
XMLFileWriter();
|
/// The caption is for message boxes to show in case of errors.
|
||||||
|
/// Might throw.
|
||||||
|
XMLFileWriter
|
||||||
|
( const wxString &outputPath, const wxString &caption,
|
||||||
|
bool keepBackup = false );
|
||||||
|
|
||||||
virtual ~XMLFileWriter();
|
virtual ~XMLFileWriter();
|
||||||
|
|
||||||
/// Open the file. Might throw XMLFileWriterException.
|
/// Close all tags and then close the file.
|
||||||
void Open(const wxString &name, const wxString &mode);
|
/// Might throw. If not, then create
|
||||||
|
/// or modify the file at the output path.
|
||||||
|
void Commit();
|
||||||
|
|
||||||
/// Close file. Might throw XMLFileWriterException.
|
/// Write to file. Might throw.
|
||||||
void Close();
|
|
||||||
|
|
||||||
/// Close file without automatically ending tags.
|
|
||||||
/// Might throw XMLFileWriterException.
|
|
||||||
void CloseWithoutEndingTags(); // for auto-save files
|
|
||||||
|
|
||||||
/// Write to file. Might throw XMLFileWriterException.
|
|
||||||
void Write(const wxString &data) override;
|
void Write(const wxString &data) override;
|
||||||
|
|
||||||
|
wxString GetBackupName() const { return mBackupName; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
};
|
void ThrowException(
|
||||||
|
const wxFileName &fileName, const wxString &caption)
|
||||||
|
{
|
||||||
|
throw FileException{ FileException::Cause::Write, fileName, caption };
|
||||||
|
}
|
||||||
|
|
||||||
///
|
/// Close file without automatically ending tags.
|
||||||
/// Exception thrown by various XMLFileWriter methods
|
/// Might throw.
|
||||||
///
|
void CloseWithoutEndingTags(); // for auto-save files
|
||||||
class XMLFileWriterException
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
XMLFileWriterException(const wxString& message) { mMessage = message; }
|
|
||||||
wxString GetMessage() const { return mMessage; }
|
|
||||||
|
|
||||||
protected:
|
const wxString mOutputPath;
|
||||||
wxString mMessage;
|
const wxString mCaption;
|
||||||
|
wxString mBackupName;
|
||||||
|
const bool mKeepBackup;
|
||||||
|
|
||||||
|
wxFFile mBackupFile;
|
||||||
};
|
};
|
||||||
|
|
||||||
///
|
///
|
||||||
|
Loading…
x
Reference in New Issue
Block a user