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,243 +738,207 @@ bool AutoSaveFile::Decode(const wxString & fileName)
|
|||
return true;
|
||||
}
|
||||
|
||||
XMLFileWriter out;
|
||||
wxString tempName;
|
||||
len = file.Length() - len;
|
||||
using Chars = ArrayOf < char >;
|
||||
using WxChars = ArrayOf < wxChar >;
|
||||
Chars buf{ len };
|
||||
if (file.Read(buf.get(), len) != len)
|
||||
{
|
||||
using Chars = ArrayOf < char >;
|
||||
using WxChars = ArrayOf < wxChar >;
|
||||
Chars buf{ len };
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.Read(buf.get(), len) != len)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
wxMemoryInputStream in(buf.get(), len);
|
||||
|
||||
wxMemoryInputStream in(buf.get(), len);
|
||||
file.Close();
|
||||
|
||||
file.Close();
|
||||
// JKC: ANSWER-ME: Is the try catch actually doing anything?
|
||||
// If it is useful, why are we not using it everywhere?
|
||||
// If it isn't useful, why are we doing it here?
|
||||
// PRL: Yes, now we are doing GuardedCall everywhere that XMLFileWriter is
|
||||
// used.
|
||||
return GuardedCall< bool >( [&] {
|
||||
XMLFileWriter out{ fileName, _("Error Decoding File") };
|
||||
|
||||
// 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?
|
||||
// If it is useful, why are we not using it everywhere?
|
||||
// If it isn't useful, why are we doing it here?
|
||||
try
|
||||
{
|
||||
out.Open(tempName, wxT("wb"));
|
||||
opened = out.IsOpened();
|
||||
}
|
||||
catch (const XMLFileWriterException&)
|
||||
{
|
||||
}
|
||||
|
||||
if (!opened)
|
||||
{
|
||||
wxRemoveFile(tempName);
|
||||
|
||||
return false;
|
||||
}
|
||||
IdMap mIds;
|
||||
IdMapArray mIdStack;
|
||||
|
||||
mIds.clear();
|
||||
|
||||
while (!in.Eof() && !out.Error())
|
||||
while ( !in.Eof() )
|
||||
{
|
||||
short id;
|
||||
|
||||
switch (in.GetC())
|
||||
{
|
||||
case FT_Push:
|
||||
{
|
||||
mIdStack.Add(mIds);
|
||||
mIds.clear();
|
||||
}
|
||||
break;
|
||||
case FT_Push:
|
||||
{
|
||||
mIdStack.Add(mIds);
|
||||
mIds.clear();
|
||||
}
|
||||
break;
|
||||
|
||||
case FT_Pop:
|
||||
{
|
||||
mIds = mIdStack[mIdStack.GetCount() - 1];
|
||||
mIdStack.RemoveAt(mIdStack.GetCount() - 1);
|
||||
}
|
||||
break;
|
||||
case FT_Pop:
|
||||
{
|
||||
mIds = mIdStack[mIdStack.GetCount() - 1];
|
||||
mIdStack.RemoveAt(mIdStack.GetCount() - 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case FT_Name:
|
||||
{
|
||||
short len;
|
||||
case FT_Name:
|
||||
{
|
||||
short len;
|
||||
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&len, sizeof(len));
|
||||
WxChars name{ len / sizeof(wxChar) };
|
||||
in.Read(name.get(), len);
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&len, sizeof(len));
|
||||
WxChars name{ len / sizeof(wxChar) };
|
||||
in.Read(name.get(), len);
|
||||
|
||||
mIds[id] = wxString(name.get(), len / sizeof(wxChar));
|
||||
}
|
||||
break;
|
||||
mIds[id] = wxString(name.get(), len / sizeof(wxChar));
|
||||
}
|
||||
break;
|
||||
|
||||
case FT_StartTag:
|
||||
{
|
||||
in.Read(&id, sizeof(id));
|
||||
case FT_StartTag:
|
||||
{
|
||||
in.Read(&id, sizeof(id));
|
||||
|
||||
out.StartTag(mIds[id]);
|
||||
}
|
||||
break;
|
||||
out.StartTag(mIds[id]);
|
||||
}
|
||||
break;
|
||||
|
||||
case FT_EndTag:
|
||||
{
|
||||
in.Read(&id, sizeof(id));
|
||||
case FT_EndTag:
|
||||
{
|
||||
in.Read(&id, sizeof(id));
|
||||
|
||||
out.EndTag(mIds[id]);
|
||||
}
|
||||
break;
|
||||
out.EndTag(mIds[id]);
|
||||
}
|
||||
break;
|
||||
|
||||
case FT_String:
|
||||
{
|
||||
int len;
|
||||
case FT_String:
|
||||
{
|
||||
int len;
|
||||
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&len, sizeof(len));
|
||||
WxChars val{ len / sizeof(wxChar) };
|
||||
in.Read(val.get(), len);
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&len, sizeof(len));
|
||||
WxChars val{ len / sizeof(wxChar) };
|
||||
in.Read(val.get(), len);
|
||||
|
||||
out.WriteAttr(mIds[id], wxString(val.get(), len / sizeof(wxChar)));
|
||||
}
|
||||
break;
|
||||
out.WriteAttr(mIds[id], wxString(val.get(), len / sizeof(wxChar)));
|
||||
}
|
||||
break;
|
||||
|
||||
case FT_Float:
|
||||
{
|
||||
float val;
|
||||
int dig;
|
||||
case FT_Float:
|
||||
{
|
||||
float val;
|
||||
int dig;
|
||||
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&val, sizeof(val));
|
||||
in.Read(&dig, sizeof(dig));
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&val, sizeof(val));
|
||||
in.Read(&dig, sizeof(dig));
|
||||
|
||||
out.WriteAttr(mIds[id], val, dig);
|
||||
}
|
||||
break;
|
||||
out.WriteAttr(mIds[id], val, dig);
|
||||
}
|
||||
break;
|
||||
|
||||
case FT_Double:
|
||||
{
|
||||
double val;
|
||||
int dig;
|
||||
case FT_Double:
|
||||
{
|
||||
double val;
|
||||
int dig;
|
||||
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&val, sizeof(val));
|
||||
in.Read(&dig, sizeof(dig));
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&val, sizeof(val));
|
||||
in.Read(&dig, sizeof(dig));
|
||||
|
||||
out.WriteAttr(mIds[id], val, dig);
|
||||
}
|
||||
break;
|
||||
out.WriteAttr(mIds[id], val, dig);
|
||||
}
|
||||
break;
|
||||
|
||||
case FT_Int:
|
||||
{
|
||||
int val;
|
||||
case FT_Int:
|
||||
{
|
||||
int val;
|
||||
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&val, sizeof(val));
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&val, sizeof(val));
|
||||
|
||||
out.WriteAttr(mIds[id], val);
|
||||
}
|
||||
break;
|
||||
out.WriteAttr(mIds[id], val);
|
||||
}
|
||||
break;
|
||||
|
||||
case FT_Bool:
|
||||
{
|
||||
bool val;
|
||||
case FT_Bool:
|
||||
{
|
||||
bool val;
|
||||
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&val, sizeof(val));
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&val, sizeof(val));
|
||||
|
||||
out.WriteAttr(mIds[id], val);
|
||||
}
|
||||
break;
|
||||
out.WriteAttr(mIds[id], val);
|
||||
}
|
||||
break;
|
||||
|
||||
case FT_Long:
|
||||
{
|
||||
long val;
|
||||
case FT_Long:
|
||||
{
|
||||
long val;
|
||||
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&val, sizeof(val));
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&val, sizeof(val));
|
||||
|
||||
out.WriteAttr(mIds[id], val);
|
||||
}
|
||||
break;
|
||||
out.WriteAttr(mIds[id], val);
|
||||
}
|
||||
break;
|
||||
|
||||
case FT_LongLong:
|
||||
{
|
||||
long long val;
|
||||
case FT_LongLong:
|
||||
{
|
||||
long long val;
|
||||
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&val, sizeof(val));
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&val, sizeof(val));
|
||||
|
||||
out.WriteAttr(mIds[id], val);
|
||||
}
|
||||
break;
|
||||
out.WriteAttr(mIds[id], val);
|
||||
}
|
||||
break;
|
||||
|
||||
case FT_SizeT:
|
||||
{
|
||||
size_t val;
|
||||
case FT_SizeT:
|
||||
{
|
||||
size_t val;
|
||||
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&val, sizeof(val));
|
||||
in.Read(&id, sizeof(id));
|
||||
in.Read(&val, sizeof(val));
|
||||
|
||||
out.WriteAttr(mIds[id], val);
|
||||
}
|
||||
break;
|
||||
out.WriteAttr(mIds[id], val);
|
||||
}
|
||||
break;
|
||||
|
||||
case FT_Data:
|
||||
{
|
||||
int len;
|
||||
case FT_Data:
|
||||
{
|
||||
int len;
|
||||
|
||||
in.Read(&len, sizeof(len));
|
||||
WxChars val{ len / sizeof(wxChar) };
|
||||
in.Read(val.get(), len);
|
||||
in.Read(&len, sizeof(len));
|
||||
WxChars val{ len / sizeof(wxChar) };
|
||||
in.Read(val.get(), len);
|
||||
|
||||
out.WriteData(wxString(val.get(), len / sizeof(wxChar)));
|
||||
}
|
||||
break;
|
||||
out.WriteData(wxString(val.get(), len / sizeof(wxChar)));
|
||||
}
|
||||
break;
|
||||
|
||||
case FT_Raw:
|
||||
{
|
||||
int len;
|
||||
case FT_Raw:
|
||||
{
|
||||
int len;
|
||||
|
||||
in.Read(&len, sizeof(len));
|
||||
WxChars val{ len / sizeof(wxChar) };
|
||||
in.Read(val.get(), len);
|
||||
in.Read(&len, sizeof(len));
|
||||
WxChars val{ len / sizeof(wxChar) };
|
||||
in.Read(val.get(), len);
|
||||
|
||||
out.Write(wxString(val.get(), len / sizeof(wxChar)));
|
||||
}
|
||||
break;
|
||||
out.Write(wxString(val.get(), len / sizeof(wxChar)));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
wxASSERT(true);
|
||||
default:
|
||||
wxASSERT(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool error = out.Error();
|
||||
|
||||
out.Close();
|
||||
out.Commit();
|
||||
|
||||
// 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 mDict;
|
||||
NameMap mNames;
|
||||
IdMap mIds;
|
||||
IdMapArray mIdStack;
|
||||
size_t mAllocSize;
|
||||
};
|
||||
|
||||
|
|
|
@ -44,44 +44,6 @@ On failure the old version is put back in place.
|
|||
#include "Legacy.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)
|
||||
// may throw
|
||||
{
|
||||
|
@ -291,42 +253,15 @@ static bool ConvertLegacyTrack(wxTextFile *f, XMLFileWriter &xmlFile)
|
|||
bool ConvertLegacyProjectFile(const wxFileName &filename)
|
||||
{
|
||||
wxTextFile f;
|
||||
XMLFileWriter xmlFile;
|
||||
int index = 0;
|
||||
wxString backupName;
|
||||
|
||||
do {
|
||||
index++;
|
||||
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);
|
||||
const wxString name = filename.GetFullPath();
|
||||
f.Open( name );
|
||||
if (!f.IsOpened())
|
||||
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"));
|
||||
|
||||
wxString label;
|
||||
|
@ -360,19 +295,15 @@ bool ConvertLegacyProjectFile(const wxFileName &filename)
|
|||
label = f.GetNextLine();
|
||||
}
|
||||
|
||||
// Close original before Commit() tries to overwrite it.
|
||||
f.Close();
|
||||
|
||||
xmlFile.EndTag(wxT("audacityproject"));
|
||||
xmlFile.Close();
|
||||
}
|
||||
catch (const XMLFileWriterException&)
|
||||
{
|
||||
// Error writing XML file (e.g. disk full)
|
||||
return false;
|
||||
}
|
||||
xmlFile.Commit();
|
||||
|
||||
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()),
|
||||
_("Opening Audacity Project"));
|
||||
|
||||
::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"));
|
||||
|
||||
return true;
|
||||
return true;
|
||||
} );
|
||||
}
|
||||
|
|
|
@ -3803,37 +3803,21 @@ bool AudacityProject::Save(bool overwrite /* = true */ ,
|
|||
}
|
||||
}
|
||||
|
||||
// Write the AUP file.
|
||||
XMLFileWriter saveFile;
|
||||
|
||||
try
|
||||
{
|
||||
saveFile.Open(mFileName, wxT("wb"));
|
||||
auto success = GuardedCall< bool >( [&] {
|
||||
// Write the AUP file.
|
||||
XMLFileWriter saveFile{ mFileName, _("Error Saving Project") };
|
||||
|
||||
WriteXMLHeader(saveFile);
|
||||
WriteXML(saveFile);
|
||||
mStrOtherNamesArray.Clear();
|
||||
|
||||
saveFile.Close();
|
||||
}
|
||||
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);
|
||||
saveFile.Commit();
|
||||
|
||||
// When XMLWriter throws an exception, it tries to close it before,
|
||||
// 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);
|
||||
}
|
||||
return true;
|
||||
} );
|
||||
|
||||
if (!success)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bWantSaveCompressed)
|
||||
mWantSaveCompressed = false; // Don't want this mode for AudacityProject::WriteXML() any more.
|
||||
|
@ -5103,7 +5087,9 @@ void AudacityProject::AutoSave()
|
|||
wxString fn = wxFileName(FileNames::AutoSaveDir(),
|
||||
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);
|
||||
|
||||
|
@ -5114,18 +5100,11 @@ void AudacityProject::AutoSave()
|
|||
|
||||
wxFFile saveFile;
|
||||
saveFile.Open(fn + wxT(".tmp"), wxT("wb"));
|
||||
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);
|
||||
return buffer.Write(saveFile);
|
||||
} );
|
||||
|
||||
if (!success)
|
||||
return;
|
||||
}
|
||||
|
||||
// Now that we have a NEW auto-save file, DELETE the old one
|
||||
DeleteCurrentAutoSaveFile();
|
||||
|
|
43
src/Tags.cpp
43
src/Tags.cpp
|
@ -1219,12 +1219,9 @@ void TagsEditor::OnSave(wxCommandEvent & WXUNUSED(event))
|
|||
return;
|
||||
}
|
||||
|
||||
// Create/Open the file
|
||||
XMLFileWriter writer;
|
||||
|
||||
try
|
||||
{
|
||||
writer.Open(fn, wxT("wb"));
|
||||
GuardedCall< void >( [&] {
|
||||
// Create/Open the file
|
||||
XMLFileWriter writer{ fn, _("Error Saving Tags File") };
|
||||
|
||||
// Remember title and track in case they're read only
|
||||
wxString title = mLocal.GetTag(TAG_TITLE);
|
||||
|
@ -1240,29 +1237,23 @@ void TagsEditor::OnSave(wxCommandEvent & WXUNUSED(event))
|
|||
mLocal.SetTag(TAG_TRACK, wxEmptyString);
|
||||
}
|
||||
|
||||
auto cleanup = finally( [&] {
|
||||
// Restore title
|
||||
if (!mEditTitle) {
|
||||
mLocal.SetTag(TAG_TITLE, title);
|
||||
}
|
||||
|
||||
// Restore track
|
||||
if (!mEditTrack) {
|
||||
mLocal.SetTag(TAG_TRACK, track);
|
||||
}
|
||||
} );
|
||||
|
||||
// Write the metadata
|
||||
mLocal.WriteXML(writer);
|
||||
|
||||
// Restore title
|
||||
if (!mEditTitle) {
|
||||
mLocal.SetTag(TAG_TITLE, title);
|
||||
}
|
||||
|
||||
// Restore track
|
||||
if (!mEditTrack) {
|
||||
mLocal.SetTag(TAG_TRACK, track);
|
||||
}
|
||||
|
||||
// Close the file
|
||||
writer.Close();
|
||||
}
|
||||
catch (const XMLFileWriterException &exception)
|
||||
{
|
||||
wxMessageBox(wxString::Format(
|
||||
_("Couldn't write to file \"%s\": %s"),
|
||||
fn.c_str(), exception.GetMessage().c_str()),
|
||||
_("Error Saving Tags File"), wxICON_ERROR, this);
|
||||
}
|
||||
writer.Commit();
|
||||
} );
|
||||
}
|
||||
|
||||
void TagsEditor::OnSaveDefaults(wxCommandEvent & WXUNUSED(event))
|
||||
|
|
|
@ -3580,6 +3580,7 @@ void EffectUIHost::OnImport(wxCommandEvent & WXUNUSED(evt))
|
|||
void EffectUIHost::OnExport(wxCommandEvent & WXUNUSED(evt))
|
||||
{
|
||||
// may throw
|
||||
// exceptions are handled in AudacityApp::OnExceptionInMainLoop
|
||||
mClient->ExportPresets();
|
||||
|
||||
return;
|
||||
|
|
|
@ -1601,27 +1601,16 @@ void EffectEqualization::SaveCurves(const wxString &fileName)
|
|||
else
|
||||
fn = fileName;
|
||||
|
||||
// Create/Open the file
|
||||
XMLFileWriter eqFile;
|
||||
const wxString fullPath{ fn.GetFullPath() };
|
||||
|
||||
try
|
||||
{
|
||||
eqFile.Open( fullPath, wxT("wb") );
|
||||
GuardedCall< void >( [&] {
|
||||
// Create/Open the file
|
||||
const wxString fullPath{ fn.GetFullPath() };
|
||||
XMLFileWriter eqFile{ fullPath, _("Error Saving Equalization Curves") };
|
||||
|
||||
// Write the curves
|
||||
WriteXML( eqFile );
|
||||
|
||||
// Close the file
|
||||
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);
|
||||
}
|
||||
eqFile.Commit();
|
||||
} );
|
||||
}
|
||||
|
||||
//
|
||||
|
|
|
@ -3562,10 +3562,7 @@ void VSTEffect::SaveFXProgram(wxMemoryBuffer & buf, int index)
|
|||
void VSTEffect::SaveXML(const wxFileName & fn)
|
||||
// may throw
|
||||
{
|
||||
XMLFileWriter xmlFile;
|
||||
|
||||
// Create/Open the file
|
||||
xmlFile.Open(fn.GetFullPath(), wxT("wb"));
|
||||
XMLFileWriter xmlFile{ fn.GetFullPath(), _("Error Saving Effect Presets") };
|
||||
|
||||
xmlFile.StartTag(wxT("vstprogrampersistence"));
|
||||
xmlFile.WriteAttr(wxT("version"), wxT("2"));
|
||||
|
@ -3616,10 +3613,7 @@ void VSTEffect::SaveXML(const wxFileName & fn)
|
|||
|
||||
xmlFile.EndTag(wxT("vstprogrampersistence"));
|
||||
|
||||
// Close the file
|
||||
xmlFile.Close();
|
||||
|
||||
return;
|
||||
xmlFile.Commit();
|
||||
}
|
||||
|
||||
bool VSTEffect::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
|
||||
|
|
|
@ -491,12 +491,15 @@ FFmpegPresets::FFmpegPresets()
|
|||
|
||||
FFmpegPresets::~FFmpegPresets()
|
||||
{
|
||||
XMLFileWriter writer;
|
||||
// FIXME: TRAP_ERR Catch XMLFileWriterException
|
||||
wxFileName xmlFileName(FileNames::DataDir(), wxT("ffmpeg_presets.xml"));
|
||||
writer.Open(xmlFileName.GetFullPath(),wxT("wb"));
|
||||
WriteXMLHeader(writer);
|
||||
WriteXML(writer);
|
||||
// We're in a destructor! Don't let exceptions out!
|
||||
GuardedCall< void >( [&] {
|
||||
wxFileName xmlFileName{ FileNames::DataDir(), wxT("ffmpeg_presets.xml") };
|
||||
XMLFileWriter writer{
|
||||
xmlFileName.GetFullPath(), _("Error Saving FFmpeg Presets") };
|
||||
WriteXMLHeader(writer);
|
||||
WriteXML(writer);
|
||||
writer.Commit();
|
||||
} );
|
||||
}
|
||||
|
||||
void FFmpegPresets::ImportPresets(wxString &filename)
|
||||
|
@ -515,11 +518,12 @@ void FFmpegPresets::ImportPresets(wxString &filename)
|
|||
|
||||
void FFmpegPresets::ExportPresets(wxString &filename)
|
||||
{
|
||||
XMLFileWriter writer;
|
||||
// FIXME: TRAP_ERR Catch XMLFileWriterException
|
||||
writer.Open(filename,wxT("wb"));
|
||||
WriteXMLHeader(writer);
|
||||
WriteXML(writer);
|
||||
GuardedCall< void >( [&] {
|
||||
XMLFileWriter writer{ filename, _("Error Saving FFmpeg Presets") };
|
||||
WriteXMLHeader(writer);
|
||||
WriteXML(writer);
|
||||
writer.Commit();
|
||||
} );
|
||||
}
|
||||
|
||||
void FFmpegPresets::GetPresetList(wxArrayString &list)
|
||||
|
|
|
@ -376,20 +376,11 @@ void KeyConfigPrefs::OnExport(wxCommandEvent & WXUNUSED(event))
|
|||
gPrefs->Write(wxT("/DefaultExportPath"), path);
|
||||
gPrefs->Flush();
|
||||
|
||||
XMLFileWriter prefFile;
|
||||
|
||||
try
|
||||
{
|
||||
prefFile.Open(file, wxT("wb"));
|
||||
GuardedCall< void >( [&] {
|
||||
XMLFileWriter prefFile{ file, _("Error Exporting Keyboard Shortcuts") };
|
||||
mManager->WriteXML(prefFile);
|
||||
prefFile.Close();
|
||||
}
|
||||
catch (const XMLFileWriterException &)
|
||||
{
|
||||
wxMessageBox(_("Couldn't write to file: ") + file,
|
||||
_("Error Exporting Keyboard Shortcuts"),
|
||||
wxOK | wxCENTRE, this);
|
||||
}
|
||||
prefFile.Commit();
|
||||
} );
|
||||
}
|
||||
|
||||
void KeyConfigPrefs::OnDefaults(wxCommandEvent & WXUNUSED(event))
|
||||
|
@ -957,20 +948,11 @@ void KeyConfigPrefs::OnExport(wxCommandEvent & WXUNUSED(event))
|
|||
gPrefs->Write(wxT("/DefaultExportPath"), path);
|
||||
gPrefs->Flush();
|
||||
|
||||
XMLFileWriter prefFile;
|
||||
|
||||
try
|
||||
{
|
||||
prefFile.Open(file, wxT("wb"));
|
||||
GuardedCall< void >( [&] {
|
||||
XMLFileWriter prefFile{ file, _("Error Exporting Keyboard Shortcuts") };
|
||||
mManager->WriteXML(prefFile);
|
||||
prefFile.Close();
|
||||
}
|
||||
catch (const XMLFileWriterException &)
|
||||
{
|
||||
wxMessageBox(_("Couldn't write to file: ") + file,
|
||||
_("Error Exporting Keyboard Shortcuts"),
|
||||
wxOK | wxCENTRE, this);
|
||||
}
|
||||
prefFile.Commit();
|
||||
} );
|
||||
}
|
||||
|
||||
void KeyConfigPrefs::OnDefaults(wxCommandEvent & WXUNUSED(event))
|
||||
|
|
|
@ -265,58 +265,107 @@ wxString XMLWriter::XMLEsc(const wxString & s)
|
|||
///
|
||||
/// 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 (IsOpened()) {
|
||||
Close();
|
||||
if (mKeepBackup) {
|
||||
int index = 0;
|
||||
wxString backupName;
|
||||
|
||||
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))
|
||||
throw XMLFileWriterException(_("Error Opening File"));
|
||||
// Don't let a destructor throw!
|
||||
GuardedCall< void >( [&] {
|
||||
if (IsOpened()) {
|
||||
// Was not committed
|
||||
auto fileName = GetName();
|
||||
CloseWithoutEndingTags();
|
||||
::wxRemoveFile( fileName );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
void XMLFileWriter::Close()
|
||||
void XMLFileWriter::Commit()
|
||||
// may throw
|
||||
{
|
||||
while (mTagstack.GetCount()) {
|
||||
EndTag(mTagstack[0]);
|
||||
}
|
||||
|
||||
auto tempPath = GetName();
|
||||
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()
|
||||
// may throw
|
||||
{
|
||||
// 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.
|
||||
if (!wxFFile::Flush())
|
||||
{
|
||||
wxFFile::Close();
|
||||
/* i18n-hint: 'flushing' means writing any remaining queued up changes
|
||||
* to disk that have not yet been written.*/
|
||||
throw XMLFileWriterException(_("Error Flushing File"));
|
||||
ThrowException( GetName(), mCaption );
|
||||
}
|
||||
|
||||
// Note that this should never fail if flushing worked.
|
||||
if (!wxFFile::Close())
|
||||
throw XMLFileWriterException(_("Error Closing File"));
|
||||
ThrowException( GetName(), mCaption );
|
||||
}
|
||||
|
||||
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
|
||||
// exception, so it can at least be deleted.
|
||||
wxFFile::Close();
|
||||
throw XMLFileWriterException(_("Error Writing to File"));
|
||||
ThrowException( GetName(), mCaption );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
#include <wx/dynarray.h>
|
||||
#include <wx/ffile.h>
|
||||
|
||||
#include "../FileException.h"
|
||||
|
||||
///
|
||||
/// XMLWriter
|
||||
///
|
||||
|
@ -60,41 +62,55 @@ class AUDACITY_DLL_API XMLWriter /* not final */ {
|
|||
///
|
||||
/// 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:
|
||||
|
||||
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();
|
||||
|
||||
/// Open the file. Might throw XMLFileWriterException.
|
||||
void Open(const wxString &name, const wxString &mode);
|
||||
/// Close all tags and then close the file.
|
||||
/// Might throw. If not, then create
|
||||
/// or modify the file at the output path.
|
||||
void Commit();
|
||||
|
||||
/// Close file. Might throw XMLFileWriterException.
|
||||
void Close();
|
||||
|
||||
/// Close file without automatically ending tags.
|
||||
/// Might throw XMLFileWriterException.
|
||||
void CloseWithoutEndingTags(); // for auto-save files
|
||||
|
||||
/// Write to file. Might throw XMLFileWriterException.
|
||||
/// Write to file. Might throw.
|
||||
void Write(const wxString &data) override;
|
||||
|
||||
wxString GetBackupName() const { return mBackupName; }
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
void ThrowException(
|
||||
const wxFileName &fileName, const wxString &caption)
|
||||
{
|
||||
throw FileException{ FileException::Cause::Write, fileName, caption };
|
||||
}
|
||||
|
||||
///
|
||||
/// Exception thrown by various XMLFileWriter methods
|
||||
///
|
||||
class XMLFileWriterException
|
||||
{
|
||||
public:
|
||||
XMLFileWriterException(const wxString& message) { mMessage = message; }
|
||||
wxString GetMessage() const { return mMessage; }
|
||||
/// Close file without automatically ending tags.
|
||||
/// Might throw.
|
||||
void CloseWithoutEndingTags(); // for auto-save files
|
||||
|
||||
protected:
|
||||
wxString mMessage;
|
||||
const wxString mOutputPath;
|
||||
const wxString mCaption;
|
||||
wxString mBackupName;
|
||||
const bool mKeepBackup;
|
||||
|
||||
wxFFile mBackupFile;
|
||||
};
|
||||
|
||||
///
|
||||
|
|
Loading…
Reference in New Issue